From e81f1079f9000892cf54b23a9572ad5d86036fca Mon Sep 17 00:00:00 2001 From: Catalin Iacob Date: Mon, 8 Jun 2026 17:29:16 +0300 Subject: scsi: core: Remove export for scsi_device_from_queue() Commit 1cea5180f2f8 ("block: remove pktcdvd driver") left behind an export that is now dead code. Remove it and move the declaration of scsi_device_from_queue() to drivers/scsi/scsi_priv.h. Signed-off-by: Catalin Iacob Link: https://patch.msgid.link/20260608-remove-pktcdvd-references-v4-1-72f88b04cc87@gmail.com Signed-off-by: Martin K. Petersen --- include/scsi/scsi_device.h | 1 - 1 file changed, 1 deletion(-) (limited to 'include') diff --git a/include/scsi/scsi_device.h b/include/scsi/scsi_device.h index 029f5115b2ea..8694eeadd753 100644 --- a/include/scsi/scsi_device.h +++ b/include/scsi/scsi_device.h @@ -408,7 +408,6 @@ void scsi_attach_vpd(struct scsi_device *sdev); void scsi_cdl_check(struct scsi_device *sdev); int scsi_cdl_enable(struct scsi_device *sdev, bool enable); -extern struct scsi_device *scsi_device_from_queue(struct request_queue *q); extern int __must_check scsi_device_get(struct scsi_device *); extern void scsi_device_put(struct scsi_device *); extern struct scsi_device *scsi_device_lookup(struct Scsi_Host *, -- cgit From 813e034925814858cc52e7de321ec4848314e15d Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Thu, 18 Jun 2026 16:36:50 +0200 Subject: soc/tegra: pmc: fix #ifdef block in header When build testing on ARM without the PMC driver, the other drivers fail to link: ld.lld: error: undefined symbol: tegra_pmc_core_domain_state_synced >>> referenced by regulators-tegra30.c >>> drivers/soc/tegra/regulators-tegra30.o:(tegra30_regulator_balance_voltage) in archive vmlinux.a >>> referenced by regulators-tegra20.c >>> drivers/soc/tegra/regulators-tegra20.o:(tegra20_core_rtc_update) in archive vmlinux.a Adapt the checks in the header to cover both cases on other architectures and without PMC. Fixes: 8318af5dd29c ("soc/tegra: pmc: Move legacy code behind CONFIG_ARM guard") Signed-off-by: Arnd Bergmann Signed-off-by: Thierry Reding --- include/soc/tegra/pmc.h | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'include') diff --git a/include/soc/tegra/pmc.h b/include/soc/tegra/pmc.h index 4bcbf19d75ac..53f6d02889db 100644 --- a/include/soc/tegra/pmc.h +++ b/include/soc/tegra/pmc.h @@ -210,7 +210,6 @@ tegra_pmc_io_pad_power_disable(struct tegra_pmc *pmc, enum tegra_io_pad id) bool tegra_pmc_cpu_is_powered(unsigned int cpuid); int tegra_pmc_cpu_power_on(unsigned int cpuid); int tegra_pmc_cpu_remove_clamping(unsigned int cpuid); -bool tegra_pmc_core_domain_state_synced(void); #if defined(CONFIG_SOC_TEGRA_PMC) && defined(CONFIG_PM_SLEEP) enum tegra_suspend_mode tegra_pmc_get_suspend_mode(void); @@ -230,6 +229,10 @@ static inline void tegra_pmc_enter_suspend_mode(enum tegra_suspend_mode mode) { } #endif +#endif + +#if defined(CONFIG_ARM) && defined(CONFIG_SOC_TEGRA_PMC) +bool tegra_pmc_core_domain_state_synced(void); #else /* needed for COMPILE_TEST */ static inline bool tegra_pmc_core_domain_state_synced(void) -- cgit From ab45ecfab540653f1ff4a8d2f2da055c82cf1640 Mon Sep 17 00:00:00 2001 From: Tanmay Kathpalia Date: Sat, 27 Jun 2026 13:14:46 -0700 Subject: dt-bindings: reset: altr: add COMBOPHY_RESET for Agilex5 Add COMBOPHY_RESET definition at index 38 for the combo PHY reset control on Altera Agilex5 SoCs. This reset is used by peripherals such as the SD/eMMC controller that share the combo PHY. Signed-off-by: Tanmay Kathpalia Acked-by: Conor Dooley Reviewed-by: Philipp Zabel Signed-off-by: Philipp Zabel --- include/dt-bindings/reset/altr,rst-mgr-s10.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'include') diff --git a/include/dt-bindings/reset/altr,rst-mgr-s10.h b/include/dt-bindings/reset/altr,rst-mgr-s10.h index 04c4d0c6fd34..c2505b9eb63e 100644 --- a/include/dt-bindings/reset/altr,rst-mgr-s10.h +++ b/include/dt-bindings/reset/altr,rst-mgr-s10.h @@ -22,7 +22,7 @@ #define USB0_RESET 35 #define USB1_RESET 36 #define NAND_RESET 37 -/* 38 is empty */ +#define COMBOPHY_RESET 38 #define SDMMC_RESET 39 #define EMAC0_OCP_RESET 40 #define EMAC1_OCP_RESET 41 -- cgit From c4a5f0071cc6d378a0e7151b60d85986aefac1f3 Mon Sep 17 00:00:00 2001 From: Cen Zhang Date: Sat, 27 Jun 2026 11:50:58 +0800 Subject: xfrm: cache the offload ifindex for netlink dumps copy_to_user_state_extra() only holds a reference to the outer xfrm_state. That does not pin x->xso.dev. NETDEV_DOWN and NETDEV_UNREGISTER can race through xfrm_dev_state_flush(), xfrm_state_delete(), and xfrm_dev_state_free(), which clears xso->dev and drops the netdev reference before the GETSA dump reaches xso_to_xuo() and reads xso->dev->ifindex. The buggy scenario involves two paths, with each column showing the order within that path: XFRM_MSG_GETSA dump path: NETDEV teardown path: 1. xfrm_get_sa() gets xfrm_state 1. xfrm_dev_state_flush() finds x 2. copy_to_user_state_extra() sees 2. xfrm_state_delete() removes x x->xso.dev from the SAD 3. copy_user_offload() calls 3. xfrm_dev_state_free() clears xso_to_xuo() xso->dev 4. xso->dev->ifindex dereferences 4. netdev_put() drops the device a detached net_device reference Avoid following the live net_device from the dump paths. Cache the attached ifindex in xfrm_dev_offload when state or policy offload is bound to a device, and serialize that snapshot instead. This preserves the user-visible XFRMA_OFFLOAD_DEV value without depending on the embedded net_device lifetime. Validation reproduced this kernel report: Oops: general protection fault Call Trace: copy_to_user_state_extra+0xb8d/0x1370 [xfrm_user] ? __pfx_copy_to_user_state_extra+0x10/0x10 [xfrm_user] ? __asan_memset+0x23/0x50 ? srso_alias_return_thunk+0x5/0xfbef5 ? __alloc_skb+0x342/0x960 ? srso_alias_return_thunk+0x5/0xfbef5 ? __asan_memset+0x23/0x50 ? srso_alias_return_thunk+0x5/0xfbef5 ? __nlmsg_put+0x147/0x1b0 dump_one_state+0x1c7/0x3e0 [xfrm_user] xfrm_state_netlink+0xcb/0x130 [xfrm_user] ? __pfx_xfrm_state_netlink+0x10/0x10 [xfrm_user] ? srso_alias_return_thunk+0x5/0xfbef5 ? xfrm_user_state_lookup.constprop.0+0x230/0x310 [xfrm_user] xfrm_get_sa+0x102/0x250 [xfrm_user] ? __pfx_xfrm_get_sa+0x10/0x10 [xfrm_user] xfrm_user_rcv_msg+0x504/0xaa0 [xfrm_user] ? __pfx_xfrm_user_rcv_msg+0x10/0x10 [xfrm_user] ? srso_alias_return_thunk+0x5/0xfbef5 ? stack_trace_save+0x8e/0xc0 ? __pfx_stack_trace_save+0x10/0x10 netlink_rcv_skb+0x11f/0x350 ? __pfx_xfrm_user_rcv_msg+0x10/0x10 [xfrm_user] ? __pfx_netlink_rcv_skb+0x10/0x10 ? __pfx_mutex_lock+0x10/0x10 ? srso_alias_return_thunk+0x5/0xfbef5 xfrm_netlink_rcv+0x65/0x80 [xfrm_user] netlink_unicast+0x600/0x870 ? __pfx_netlink_unicast+0x10/0x10 ? srso_alias_return_thunk+0x5/0xfbef5 ? __pfx_stack_trace_save+0x10/0x10 netlink_sendmsg+0x75d/0xc10 ? __pfx_netlink_sendmsg+0x10/0x10 ? srso_alias_return_thunk+0x5/0xfbef5 ____sys_sendmsg+0x77a/0x900 ? srso_alias_return_thunk+0x5/0xfbef5 ? __pfx_____sys_sendmsg+0x10/0x10 ? __pfx_copy_msghdr_from_user+0x10/0x10 ? release_sock+0x1a/0x1d0 ? srso_alias_return_thunk+0x5/0xfbef5 ? netlink_insert+0x143/0xec0 ___sys_sendmsg+0xff/0x180 ? __pfx____sys_sendmsg+0x10/0x10 ? _raw_spin_lock_irqsave+0x85/0xe0 ? do_getsockname+0xf9/0x170 ? srso_alias_return_thunk+0x5/0xfbef5 ? fdget+0x53/0x3b0 __sys_sendmsg+0x111/0x1a0 ? __pfx___sys_sendmsg+0x10/0x10 ? srso_alias_return_thunk+0x5/0xfbef5 ? __sys_getsockname+0x8c/0x100 do_syscall_64+0x102/0x5a0 entry_SYSCALL_64_after_hwframe+0x77/0x7f Fixes: 07b87f9eea0c ("xfrm: Fix unregister netdevice hang on hardware offload.") Assisted-by: Codex:gpt-5.5 Signed-off-by: Cen Zhang Signed-off-by: Steffen Klassert --- include/net/xfrm.h | 2 ++ net/xfrm/xfrm_device.c | 1 + net/xfrm/xfrm_state.c | 1 + net/xfrm/xfrm_user.c | 38 +++++++++++++++++++++++++++++--------- 4 files changed, 33 insertions(+), 9 deletions(-) (limited to 'include') diff --git a/include/net/xfrm.h b/include/net/xfrm.h index 519a0156a05c..a6d69aaa6cd2 100644 --- a/include/net/xfrm.h +++ b/include/net/xfrm.h @@ -162,6 +162,8 @@ struct xfrm_dev_offload { */ struct net_device *real_dev; unsigned long offload_handle; + /* Snapshot the attached device index for dump paths. */ + int ifindex; u8 dir : 2; u8 type : 2; u8 flags : 2; diff --git a/net/xfrm/xfrm_device.c b/net/xfrm/xfrm_device.c index aec1e1184a71..f153bf695b9d 100644 --- a/net/xfrm/xfrm_device.c +++ b/net/xfrm/xfrm_device.c @@ -321,6 +321,7 @@ int xfrm_dev_state_add(struct net *net, struct xfrm_state *x, } xso->dev = dev; + xso->ifindex = dev->ifindex; netdev_tracker_alloc(dev, &xso->dev_tracker, GFP_ATOMIC); if (xuo->flags & XFRM_OFFLOAD_INBOUND) diff --git a/net/xfrm/xfrm_state.c b/net/xfrm/xfrm_state.c index 08ba6805ddb3..10e5a1a95fe3 100644 --- a/net/xfrm/xfrm_state.c +++ b/net/xfrm/xfrm_state.c @@ -1547,6 +1547,7 @@ found: xso->type = XFRM_DEV_OFFLOAD_PACKET; xso->dir = xdo->dir; xso->dev = dev; + xso->ifindex = dev->ifindex; xso->flags = XFRM_DEV_OFFLOAD_FLAG_ACQ; netdev_hold(dev, &xso->dev_tracker, GFP_ATOMIC); error = dev->xfrmdev_ops->xdo_dev_state_add(dev, x, diff --git a/net/xfrm/xfrm_user.c b/net/xfrm/xfrm_user.c index 6384795ee6b2..0eb87fc998d1 100644 --- a/net/xfrm/xfrm_user.c +++ b/net/xfrm/xfrm_user.c @@ -1201,17 +1201,26 @@ static int copy_sec_ctx(struct xfrm_sec_ctx *s, struct sk_buff *skb) return 0; } -static void xso_to_xuo(const struct xfrm_dev_offload *xso, - struct xfrm_user_offload *xuo) +static void xso_to_xuo_ifindex(const struct xfrm_dev_offload *xso, int ifindex, + struct xfrm_user_offload *xuo) { - xuo->ifindex = xso->dev->ifindex; + xuo->ifindex = ifindex; if (xso->dir == XFRM_DEV_OFFLOAD_IN) xuo->flags = XFRM_OFFLOAD_INBOUND; if (xso->type == XFRM_DEV_OFFLOAD_PACKET) xuo->flags |= XFRM_OFFLOAD_PACKET; } -static int copy_user_offload(struct xfrm_dev_offload *xso, struct sk_buff *skb) +#ifdef CONFIG_XFRM_MIGRATE +static void xso_to_xuo(const struct xfrm_dev_offload *xso, + struct xfrm_user_offload *xuo) +{ + xso_to_xuo_ifindex(xso, xso->dev->ifindex, xuo); +} +#endif + +static int copy_user_offload_ifindex(const struct xfrm_dev_offload *xso, + int ifindex, struct sk_buff *skb) { struct xfrm_user_offload *xuo; struct nlattr *attr; @@ -1222,11 +1231,22 @@ static int copy_user_offload(struct xfrm_dev_offload *xso, struct sk_buff *skb) xuo = nla_data(attr); memset(xuo, 0, sizeof(*xuo)); - xso_to_xuo(xso, xuo); + xso_to_xuo_ifindex(xso, ifindex, xuo); return 0; } +static int copy_user_offload(struct xfrm_dev_offload *xso, struct sk_buff *skb) +{ + return copy_user_offload_ifindex(xso, xso->dev->ifindex, skb); +} + +static int copy_user_state_offload(const struct xfrm_dev_offload *xso, + struct sk_buff *skb) +{ + return copy_user_offload_ifindex(xso, READ_ONCE(xso->ifindex), skb); +} + static bool xfrm_redact(void) { return IS_ENABLED(CONFIG_SECURITY) && @@ -1433,8 +1453,8 @@ static int copy_to_user_state_extra(struct xfrm_state *x, &x->replay); if (ret) goto out; - if(x->xso.dev) - ret = copy_user_offload(&x->xso, skb); + if (READ_ONCE(x->xso.dev)) + ret = copy_user_state_offload(&x->xso, skb); if (ret) goto out; if (x->if_id) { @@ -4046,8 +4066,8 @@ static inline unsigned int xfrm_sa_len(struct xfrm_state *x) l += nla_total_size(sizeof(*x->coaddr)); if (x->props.extra_flags) l += nla_total_size(sizeof(x->props.extra_flags)); - if (x->xso.dev) - l += nla_total_size(sizeof(struct xfrm_user_offload)); + if (READ_ONCE(x->xso.dev)) + l += nla_total_size(sizeof(struct xfrm_user_offload)); if (x->props.smark.v | x->props.smark.m) { l += nla_total_size(sizeof(x->props.smark.v)); l += nla_total_size(sizeof(x->props.smark.m)); -- cgit From 2b0eab425e1f658d8fe1df7590e3b9af5959505e Mon Sep 17 00:00:00 2001 From: Peddolla Harshavardhan Reddy Date: Fri, 3 Jul 2026 13:55:23 +0530 Subject: wifi: cfg80211: convert pmsr_free_wk to wiphy_work to fix deadlock When a netlink socket that owns a PMSR session is closed, cfg80211_release_pmsr() clears the request's nl_portid and queues pmsr_free_wk to call cfg80211_pmsr_process_abort() asynchronously. If the interface tears down concurrently, cfg80211_pmsr_wdev_down() is called under wiphy_lock and calls cancel_work_sync(&pmsr_free_wk) to wait for any running work. The work function acquires wiphy_lock via guard(wiphy) before calling process_abort. This is a deadlock: wdev_down holds wiphy_lock and blocks inside cancel_work_sync(); pmsr_free_wk blocks trying to acquire that same wiphy_lock. Neither thread can proceed. The same deadlock is reachable from cfg80211_leave_locked(), which calls cfg80211_pmsr_wdev_down() for all interface types under wiphy_lock. Fix this by converting pmsr_free_wk from a plain work_struct to a wiphy_work. The wiphy_work dispatcher holds wiphy_lock when running work items, so the explicit guard(wiphy) in the work function is no longer needed. wiphy_work_cancel() can be called safely while holding wiphy_lock - since wiphy_lock prevents the work from running concurrently, wiphy_work_cancel() never blocks, eliminating the deadlock. Remove the cancel_work_sync() for pmsr_free_wk from the NETDEV_GOING_DOWN handler. cfg80211_leave(), called unconditionally just before it, already cancels any pending work under wiphy_lock via wiphy_work_cancel() inside cfg80211_pmsr_wdev_down(). Fixes: 6dccbc9f3e1d ("wifi: cfg80211: cancel pmsr_free_wk in cfg80211_pmsr_wdev_down") Signed-off-by: Peddolla Harshavardhan Reddy Link: https://patch.msgid.link/20260703082523.2629324-1-peddolla.reddy@oss.qualcomm.com Signed-off-by: Johannes Berg --- include/net/cfg80211.h | 2 +- net/wireless/core.c | 3 +-- net/wireless/core.h | 2 +- net/wireless/pmsr.c | 8 +++----- 4 files changed, 6 insertions(+), 9 deletions(-) (limited to 'include') diff --git a/include/net/cfg80211.h b/include/net/cfg80211.h index 8188ad200de5..3751a1d74765 100644 --- a/include/net/cfg80211.h +++ b/include/net/cfg80211.h @@ -7265,7 +7265,7 @@ struct wireless_dev { struct list_head pmsr_list; spinlock_t pmsr_lock; - struct work_struct pmsr_free_wk; + struct wiphy_work pmsr_free_wk; unsigned long unprot_beacon_reported; diff --git a/net/wireless/core.c b/net/wireless/core.c index 2c729a7aca12..082f0ee12f1b 100644 --- a/net/wireless/core.c +++ b/net/wireless/core.c @@ -1614,7 +1614,7 @@ void cfg80211_init_wdev(struct wireless_dev *wdev) INIT_LIST_HEAD(&wdev->mgmt_registrations); INIT_LIST_HEAD(&wdev->pmsr_list); spin_lock_init(&wdev->pmsr_lock); - INIT_WORK(&wdev->pmsr_free_wk, cfg80211_pmsr_free_wk); + wiphy_work_init(&wdev->pmsr_free_wk, cfg80211_pmsr_free_wk); #ifdef CONFIG_CFG80211_WEXT wdev->wext.default_key = -1; @@ -1748,7 +1748,6 @@ static int cfg80211_netdev_notifier_call(struct notifier_block *nb, cfg80211_remove_links(wdev); /* since we just did cfg80211_leave() nothing to do there */ cancel_work_sync(&wdev->disconnect_wk); - cancel_work_sync(&wdev->pmsr_free_wk); break; case NETDEV_DOWN: wiphy_lock(&rdev->wiphy); diff --git a/net/wireless/core.h b/net/wireless/core.h index df47ed6208a5..f60c66b88677 100644 --- a/net/wireless/core.h +++ b/net/wireless/core.h @@ -586,7 +586,7 @@ cfg80211_get_6ghz_power_type(const u8 *elems, size_t elems_len, void cfg80211_release_pmsr(struct wireless_dev *wdev, u32 portid); void cfg80211_pmsr_wdev_down(struct wireless_dev *wdev); -void cfg80211_pmsr_free_wk(struct work_struct *work); +void cfg80211_pmsr_free_wk(struct wiphy *wiphy, struct wiphy_work *work); void cfg80211_remove_link(struct wireless_dev *wdev, unsigned int link_id); void cfg80211_remove_links(struct wireless_dev *wdev); diff --git a/net/wireless/pmsr.c b/net/wireless/pmsr.c index c8447448f3a5..2c8db33d9c30 100644 --- a/net/wireless/pmsr.c +++ b/net/wireless/pmsr.c @@ -807,13 +807,11 @@ static void cfg80211_pmsr_process_abort(struct wireless_dev *wdev) } } -void cfg80211_pmsr_free_wk(struct work_struct *work) +void cfg80211_pmsr_free_wk(struct wiphy *wiphy, struct wiphy_work *work) { struct wireless_dev *wdev = container_of(work, struct wireless_dev, pmsr_free_wk); - guard(wiphy)(wdev->wiphy); - cfg80211_pmsr_process_abort(wdev); } @@ -829,7 +827,7 @@ void cfg80211_pmsr_wdev_down(struct wireless_dev *wdev) } spin_unlock_bh(&wdev->pmsr_lock); - cancel_work_sync(&wdev->pmsr_free_wk); + wiphy_work_cancel(wdev->wiphy, &wdev->pmsr_free_wk); if (found) cfg80211_pmsr_process_abort(wdev); @@ -844,7 +842,7 @@ void cfg80211_release_pmsr(struct wireless_dev *wdev, u32 portid) list_for_each_entry(req, &wdev->pmsr_list, list) { if (req->nl_portid == portid) { req->nl_portid = 0; - schedule_work(&wdev->pmsr_free_wk); + wiphy_work_queue(wdev->wiphy, &wdev->pmsr_free_wk); } } spin_unlock_bh(&wdev->pmsr_lock); -- cgit From 293baeae9b2434a3e432629d7720b5603db2d77e Mon Sep 17 00:00:00 2001 From: Zhao Li Date: Fri, 12 Jun 2026 01:35:07 +0800 Subject: wifi: ieee80211: validate MLE common info length ieee80211_mle_common_size() uses the first common-info octet as the common information length for all known MLE types. However, ieee80211_mle_size_ok() only validates that octet for Basic, Probe Request, and TDLS MLEs. Reconfiguration MLEs also skipped the length octet when calculating the minimum common size, and Priority Access MLEs skipped validation of the advertised common information length. Account for the Reconfiguration common-info length octet and validate the advertised common information length for all known MLE types. Keep unknown-type handling unchanged. Fixes: 0f48b8b88aa9 ("wifi: ieee80211: add definitions for multi-link element") Cc: stable@vger.kernel.org Signed-off-by: Zhao Li Link: https://patch.msgid.link/20260611173506.36838-2-enderaoelyther@gmail.com [remove now misleading comment] Signed-off-by: Johannes Berg --- include/linux/ieee80211-eht.h | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) (limited to 'include') diff --git a/include/linux/ieee80211-eht.h b/include/linux/ieee80211-eht.h index 18f9c662cf4c..c109722b1969 100644 --- a/include/linux/ieee80211-eht.h +++ b/include/linux/ieee80211-eht.h @@ -857,7 +857,7 @@ static inline bool ieee80211_mle_size_ok(const u8 *data, size_t len) const struct ieee80211_multi_link_elem *mle = (const void *)data; u8 fixed = sizeof(*mle); u8 common = 0; - bool check_common_len = false; + u8 common_len; u16 control; if (!data || len < fixed) @@ -868,7 +868,6 @@ static inline bool ieee80211_mle_size_ok(const u8 *data, size_t len) switch (u16_get_bits(control, IEEE80211_ML_CONTROL_TYPE)) { case IEEE80211_ML_CONTROL_TYPE_BASIC: common += sizeof(struct ieee80211_mle_basic_common_info); - check_common_len = true; if (control & IEEE80211_MLC_BASIC_PRES_LINK_ID) common += 1; if (control & IEEE80211_MLC_BASIC_PRES_BSS_PARAM_CH_CNT) @@ -888,9 +887,9 @@ static inline bool ieee80211_mle_size_ok(const u8 *data, size_t len) common += sizeof(struct ieee80211_mle_preq_common_info); if (control & IEEE80211_MLC_PREQ_PRES_MLD_ID) common += 1; - check_common_len = true; break; case IEEE80211_ML_CONTROL_TYPE_RECONF: + common += 1; if (control & IEEE80211_MLC_RECONF_PRES_MLD_MAC_ADDR) common += ETH_ALEN; if (control & IEEE80211_MLC_RECONF_PRES_EML_CAPA) @@ -902,7 +901,6 @@ static inline bool ieee80211_mle_size_ok(const u8 *data, size_t len) break; case IEEE80211_ML_CONTROL_TYPE_TDLS: common += sizeof(struct ieee80211_mle_tdls_common_info); - check_common_len = true; break; case IEEE80211_ML_CONTROL_TYPE_PRIO_ACCESS: common = ETH_ALEN + 1; @@ -915,11 +913,9 @@ static inline bool ieee80211_mle_size_ok(const u8 *data, size_t len) if (len < fixed + common) return false; - if (!check_common_len) - return true; + common_len = mle->variable[0]; - /* if present, common length is the first octet there */ - return mle->variable[0] >= common; + return common_len >= common && common_len <= len - fixed; } /** -- cgit From 0c2ed186bbe14304415476d6707b747dddcd8583 Mon Sep 17 00:00:00 2001 From: Cen Zhang Date: Mon, 6 Jul 2026 23:24:18 +0800 Subject: wifi: cfg80211: use wiphy work for socket owner autodisconnect nl80211_netlink_notify() walks the cfg80211 wireless device list when a NETLINK_GENERIC socket is released. If the socket owns a connection, the notifier queues the embedded wdev->disconnect_wk work item. That work is a plain work_struct today. NETDEV_GOING_DOWN cancels it, but a NETLINK_URELEASE notifier that already observed conn_owner_nlportid can queue it after that cancel returns. _cfg80211_unregister_wdev() then removes the wdev from the list and waits for RCU readers, but synchronize_net() does not drain work queued by such a reader. Make the autodisconnect work a wiphy_work instead. The callback already needs the wiphy mutex, and wiphy_work runs under that mutex. This lets teardown cancel pending autodisconnect work while holding the mutex, without a cancel_work_sync() vs. worker locking concern. Also cancel the wiphy work after list_del_rcu() and synchronize_net(). Any NETLINK_URELEASE notifier that had already reached the wdev list has then either queued the work and it is removed, or can no longer find the wdev. Fixes: bd2522b16884 ("cfg80211: NL80211_ATTR_SOCKET_OWNER support for CMD_CONNECT") Suggested-by: Johannes Berg Assisted-by: Codex:gpt-5.5 Signed-off-by: Cen Zhang Link: https://patch.msgid.link/20260706152418.779226-1-zzzccc427@gmail.com Signed-off-by: Johannes Berg --- include/net/cfg80211.h | 2 +- net/wireless/core.c | 10 ++++++---- net/wireless/core.h | 2 +- net/wireless/nl80211.c | 3 ++- net/wireless/sme.c | 6 ++---- 5 files changed, 12 insertions(+), 11 deletions(-) (limited to 'include') diff --git a/include/net/cfg80211.h b/include/net/cfg80211.h index 3751a1d74765..f5abf1db7558 100644 --- a/include/net/cfg80211.h +++ b/include/net/cfg80211.h @@ -7228,7 +7228,7 @@ struct wireless_dev { enum ieee80211_bss_type conn_bss_type; u32 conn_owner_nlportid; - struct work_struct disconnect_wk; + struct wiphy_work disconnect_wk; u8 disconnect_bssid[ETH_ALEN]; struct list_head event_list; diff --git a/net/wireless/core.c b/net/wireless/core.c index 082f0ee12f1b..610238d723ff 100644 --- a/net/wireless/core.c +++ b/net/wireless/core.c @@ -1425,6 +1425,7 @@ static void _cfg80211_unregister_wdev(struct wireless_dev *wdev, list_del_rcu(&wdev->list); synchronize_net(); rdev->devlist_generation++; + wiphy_work_cancel(wdev->wiphy, &wdev->disconnect_wk); cfg80211_mlme_purge_registrations(wdev); @@ -1638,7 +1639,7 @@ void cfg80211_init_wdev(struct wireless_dev *wdev) wdev->iftype == NL80211_IFTYPE_ADHOC) && !wdev->use_4addr) wdev->netdev->priv_flags |= IFF_DONT_BRIDGE; - INIT_WORK(&wdev->disconnect_wk, cfg80211_autodisconnect_wk); + wiphy_work_init(&wdev->disconnect_wk, cfg80211_autodisconnect_wk); } void cfg80211_register_wdev(struct cfg80211_registered_device *rdev, @@ -1744,10 +1745,11 @@ static int cfg80211_netdev_notifier_call(struct notifier_block *nb, break; case NETDEV_GOING_DOWN: cfg80211_leave(rdev, wdev, -1); - scoped_guard(wiphy, &rdev->wiphy) + scoped_guard(wiphy, &rdev->wiphy) { cfg80211_remove_links(wdev); - /* since we just did cfg80211_leave() nothing to do there */ - cancel_work_sync(&wdev->disconnect_wk); + /* since we just did cfg80211_leave() nothing to do there */ + wiphy_work_cancel(wdev->wiphy, &wdev->disconnect_wk); + } break; case NETDEV_DOWN: wiphy_lock(&rdev->wiphy); diff --git a/net/wireless/core.h b/net/wireless/core.h index f60c66b88677..ac6ce9f967ec 100644 --- a/net/wireless/core.h +++ b/net/wireless/core.h @@ -428,7 +428,7 @@ void __cfg80211_port_authorized(struct wireless_dev *wdev, const u8 *peer_addr, const u8 *td_bitmap, u8 td_bitmap_len); int cfg80211_mgd_wext_connect(struct cfg80211_registered_device *rdev, struct wireless_dev *wdev); -void cfg80211_autodisconnect_wk(struct work_struct *work); +void cfg80211_autodisconnect_wk(struct wiphy *wiphy, struct wiphy_work *work); /* SME implementation */ void cfg80211_conn_work(struct work_struct *work); diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c index 625c99cf70a3..5adcb6bd0fc5 100644 --- a/net/wireless/nl80211.c +++ b/net/wireless/nl80211.c @@ -22954,7 +22954,8 @@ static int nl80211_netlink_notify(struct notifier_block * nb, wdev->nl_owner_dead = true; schedule_work(&rdev->destroy_work); } else if (wdev->conn_owner_nlportid == notify->portid) { - schedule_work(&wdev->disconnect_wk); + wiphy_work_queue(wdev->wiphy, + &wdev->disconnect_wk); } cfg80211_release_pmsr(wdev, notify->portid); diff --git a/net/wireless/sme.c b/net/wireless/sme.c index b451df3096dd..2a719b5c487e 100644 --- a/net/wireless/sme.c +++ b/net/wireless/sme.c @@ -1578,13 +1578,11 @@ int cfg80211_disconnect(struct cfg80211_registered_device *rdev, * Used to clean up after the connection / connection attempt owner socket * disconnects */ -void cfg80211_autodisconnect_wk(struct work_struct *work) +void cfg80211_autodisconnect_wk(struct wiphy *wiphy, struct wiphy_work *work) { struct wireless_dev *wdev = container_of(work, struct wireless_dev, disconnect_wk); - struct cfg80211_registered_device *rdev = wiphy_to_rdev(wdev->wiphy); - - guard(wiphy)(wdev->wiphy); + struct cfg80211_registered_device *rdev = wiphy_to_rdev(wiphy); if (wdev->conn_owner_nlportid) { switch (wdev->iftype) { -- cgit From a6b49d27c17909608d54523220bb6f3498d4a1df Mon Sep 17 00:00:00 2001 From: Sebastian Ene Date: Thu, 2 Jul 2026 10:38:41 +0000 Subject: KVM: arm64: Validate the offset to the mem access descriptor Prevent the pKVM hypervisor from making assumptions that the endpoint memory access descriptor (EMAD) comes right after the FF-A memory region header. Prior to FF-A version 1.1 the header of the memory region didn't contain an offset to the endpoint memory access descriptor. The layout of a memory transaction looks like this from 1.1 onward: Type | Field name | Offset [ Header | ffa_mem_region | 0 EMAD 1 | ffa_mem_region_attributes) | ffa_mem_region.ep_mem_offset ] Verify that the offset to the first endpoint memory access descriptor is within the mailbox buffer bounds. Also, fix one hardcoded sizeof(struct ffa_mem_region_attributes) that should be replaced ffa_emad_size_get() for compatibility with FFA v1.0. Fixes: 42fb33dde42b ("KVM: arm64: Use FF-A 1.1 with pKVM") Signed-off-by: Mostafa Saleh Signed-off-by: Sebastian Ene Reviewed-by: Vincent Donnefort Link: https://patch.msgid.link/20260702103848.1647249-5-sebastianene@google.com Signed-off-by: Marc Zyngier --- arch/arm64/kvm/hyp/nvhe/ffa.c | 27 +++++++++++++++++++-------- include/linux/arm_ffa.h | 7 +++++++ 2 files changed, 26 insertions(+), 8 deletions(-) (limited to 'include') diff --git a/arch/arm64/kvm/hyp/nvhe/ffa.c b/arch/arm64/kvm/hyp/nvhe/ffa.c index 41cc4c1bafeb..2e7ab7e3319d 100644 --- a/arch/arm64/kvm/hyp/nvhe/ffa.c +++ b/arch/arm64/kvm/hyp/nvhe/ffa.c @@ -476,11 +476,12 @@ static void __do_ffa_mem_xfer(const u64 func_id, DECLARE_REG(u32, fraglen, ctxt, 2); DECLARE_REG(u64, addr_mbz, ctxt, 3); DECLARE_REG(u32, npages_mbz, ctxt, 4); + u32 offset, nr_ranges, checked_offset, em_mem_access_off; struct ffa_mem_region_attributes *ep_mem_access; struct ffa_composite_mem_region *reg; struct ffa_mem_region *buf; - u32 offset, nr_ranges, checked_offset; int ret = 0; + size_t mem_region_len = FFA_MEM_REGION_SZ(hyp_ffa_version); if (addr_mbz || npages_mbz || fraglen > len || fraglen > KVM_FFA_MBOX_NR_PAGES * PAGE_SIZE) { @@ -488,8 +489,7 @@ static void __do_ffa_mem_xfer(const u64 func_id, goto out; } - if (fraglen < sizeof(struct ffa_mem_region) + - sizeof(struct ffa_mem_region_attributes)) { + if (fraglen < mem_region_len + ffa_emad_size_get(hyp_ffa_version)) { ret = FFA_RET_INVALID_PARAMETERS; goto out; } @@ -508,8 +508,13 @@ static void __do_ffa_mem_xfer(const u64 func_id, buf = hyp_buffers.tx; memcpy(buf, host_buffers.tx, fraglen); - ep_mem_access = (void *)buf + - ffa_mem_desc_offset(buf, 0, hyp_ffa_version); + em_mem_access_off = ffa_mem_desc_offset(buf, 0, hyp_ffa_version); + if ((u64)em_mem_access_off + ffa_emad_size_get(hyp_ffa_version) > fraglen) { + ret = FFA_RET_INVALID_PARAMETERS; + goto out_unlock; + } + + ep_mem_access = (void *)buf + em_mem_access_off; offset = ep_mem_access->composite_off; if (!offset || buf->ep_count != 1 || buf->sender_id != HOST_FFA_ID) { ret = FFA_RET_INVALID_PARAMETERS; @@ -574,9 +579,9 @@ static void do_ffa_mem_reclaim(struct arm_smccc_1_2_regs *res, DECLARE_REG(u32, handle_lo, ctxt, 1); DECLARE_REG(u32, handle_hi, ctxt, 2); DECLARE_REG(u32, flags, ctxt, 3); + u32 offset, len, fraglen, fragoff, em_mem_access_off; struct ffa_mem_region_attributes *ep_mem_access; struct ffa_composite_mem_region *reg; - u32 offset, len, fraglen, fragoff; struct ffa_mem_region *buf; int ret = 0; u64 handle; @@ -599,8 +604,14 @@ static void do_ffa_mem_reclaim(struct arm_smccc_1_2_regs *res, len = res->a1; fraglen = res->a2; - ep_mem_access = (void *)buf + - ffa_mem_desc_offset(buf, 0, hyp_ffa_version); + em_mem_access_off = ffa_mem_desc_offset(buf, 0, hyp_ffa_version); + if ((u64)em_mem_access_off + ffa_emad_size_get(hyp_ffa_version) > fraglen) { + ret = FFA_RET_INVALID_PARAMETERS; + ffa_rx_release(res); + goto out_unlock; + } + + ep_mem_access = (void *)buf + em_mem_access_off; offset = ep_mem_access->composite_off; /* * We can trust the SPMD to get this right, but let's at least diff --git a/include/linux/arm_ffa.h b/include/linux/arm_ffa.h index 81e603839c4a..3c91d4c4153c 100644 --- a/include/linux/arm_ffa.h +++ b/include/linux/arm_ffa.h @@ -421,6 +421,13 @@ struct ffa_mem_region { #define FFA_EMAD_HAS_IMPDEF_FIELD(version) ((version) >= FFA_VERSION_1_2) #define FFA_MEM_REGION_HAS_EP_MEM_OFFSET(version) ((version) > FFA_VERSION_1_0) +/* The layout changed from FFA_VERSION_1_0 and the region includes an + * ep_mem_offset. + */ +#define FFA_MEM_REGION_SZ(version) (!FFA_MEM_REGION_HAS_EP_MEM_OFFSET((version)) ?\ + offsetof(struct ffa_mem_region, ep_mem_offset) :\ + sizeof(struct ffa_mem_region)) + static inline u32 ffa_emad_size_get(u32 ffa_version) { u32 sz; -- cgit From b4d961351aa84fdf0148783fb1f3a1391b8a0adb Mon Sep 17 00:00:00 2001 From: Sebastian Ene Date: Thu, 2 Jul 2026 10:38:39 +0000 Subject: firmware: arm_ffa: Fix Endpoint Memory Access Descriptor offset calculation Use the descriptor's `ep_mem_offset` to calculate the start of the endpoint memory access array and to comply with the FF-A spec instead of defaulting to `sizeof(struct ffa_mem_region)`. This requires moving `ffa_mem_region_additional_setup()` earlier in the setup flow. Also, add sanity checks to ensure the calculated descriptor offsets do not exceed `max_fragsize`. Fixes: 113580530ee7 ("firmware: arm_ffa: Update memory descriptor to support v1.1 format") Reviewed-by: Sudeep Holla Signed-off-by: Mostafa Saleh Signed-off-by: Sebastian Ene Link: https://patch.msgid.link/20260702103848.1647249-3-sebastianene@google.com Signed-off-by: Marc Zyngier --- drivers/firmware/arm_ffa/driver.c | 20 +++++++++++++++----- include/linux/arm_ffa.h | 2 +- 2 files changed, 16 insertions(+), 6 deletions(-) (limited to 'include') diff --git a/drivers/firmware/arm_ffa/driver.c b/drivers/firmware/arm_ffa/driver.c index b700b2e93e72..0ad323decada 100644 --- a/drivers/firmware/arm_ffa/driver.c +++ b/drivers/firmware/arm_ffa/driver.c @@ -685,19 +685,30 @@ ffa_setup_and_transmit(u32 func_id, void *buffer, u32 max_fragsize, struct ffa_composite_mem_region *composite; struct ffa_mem_region_addr_range *constituents; struct ffa_mem_region_attributes *ep_mem_access; - u32 idx, frag_len, length, buf_sz = 0, num_entries = sg_nents(args->sg); + u32 idx, frag_len, length, buf_sz = 0, num_entries = sg_nents(args->sg), ep_offset; + u32 emad_end, emad_size = ffa_emad_size_get(drv_info->version); mem_region->tag = args->tag; mem_region->flags = args->flags; mem_region->sender_id = drv_info->vm_id; mem_region->attributes = ffa_memory_attributes_get(func_id); + + ffa_mem_region_additional_setup(drv_info->version, mem_region); composite_offset = ffa_mem_desc_offset(buffer, args->nattrs, drv_info->version); + if (composite_offset + sizeof(*composite) > max_fragsize) + return -ENXIO; for (idx = 0; idx < args->nattrs; idx++) { - ep_mem_access = buffer + - ffa_mem_desc_offset(buffer, idx, drv_info->version); - memset(ep_mem_access, 0, ffa_emad_size_get(drv_info->version)); + ep_offset = ffa_mem_desc_offset(buffer, idx, drv_info->version); + if (check_add_overflow(ep_offset, emad_size, &emad_end)) + return -ENXIO; + + if (emad_end > max_fragsize) + return -ENXIO; + + ep_mem_access = buffer + ep_offset; + memset(ep_mem_access, 0, emad_size); ep_mem_access->receiver = args->attrs[idx].receiver; ep_mem_access->attrs = args->attrs[idx].attrs; ep_mem_access->composite_off = composite_offset; @@ -707,7 +718,6 @@ ffa_setup_and_transmit(u32 func_id, void *buffer, u32 max_fragsize, } mem_region->handle = 0; mem_region->ep_count = args->nattrs; - ffa_mem_region_additional_setup(drv_info->version, mem_region); composite = buffer + composite_offset; composite->total_pg_cnt = ffa_get_num_pages_sg(args->sg); diff --git a/include/linux/arm_ffa.h b/include/linux/arm_ffa.h index 3c91d4c4153c..c6a9ffd564f7 100644 --- a/include/linux/arm_ffa.h +++ b/include/linux/arm_ffa.h @@ -452,7 +452,7 @@ ffa_mem_desc_offset(struct ffa_mem_region *buf, int count, u32 ffa_version) if (!FFA_MEM_REGION_HAS_EP_MEM_OFFSET(ffa_version)) offset += offsetof(struct ffa_mem_region, ep_mem_offset); else - offset += sizeof(struct ffa_mem_region); + offset += buf->ep_mem_offset; return offset; } -- cgit From 6aa38ef0eab32df5409b72d62509de6ba09cea50 Mon Sep 17 00:00:00 2001 From: Guoniu Zhou Date: Tue, 9 Jun 2026 14:26:40 +0800 Subject: dt-bindings: power: imx93: Add MIPI PHY power domain Add MIPI PHY power domain for shared PHY resources used by both MIPI DSI and CSI blocks. Signed-off-by: Guoniu Zhou Reviewed-by: Frank Li Acked-by: Krzysztof Kozlowski Reviewed-by: Peng Fan Fixes: e9aa77d413c9 ("soc: imx: add i.MX93 media blk ctrl driver") Cc: stable@vger.kernel.org Signed-off-by: Ulf Hansson --- include/dt-bindings/power/fsl,imx93-power.h | 1 + 1 file changed, 1 insertion(+) (limited to 'include') diff --git a/include/dt-bindings/power/fsl,imx93-power.h b/include/dt-bindings/power/fsl,imx93-power.h index 17f9f015bf7d..071221fe5c57 100644 --- a/include/dt-bindings/power/fsl,imx93-power.h +++ b/include/dt-bindings/power/fsl,imx93-power.h @@ -11,5 +11,6 @@ #define IMX93_MEDIABLK_PD_PXP 2 #define IMX93_MEDIABLK_PD_LCDIF 3 #define IMX93_MEDIABLK_PD_ISI 4 +#define IMX93_MEDIABLK_PD_MIPI_PHY 5 #endif -- cgit From dccf3b1798b70f94e958b3d00b83010399e6fb05 Mon Sep 17 00:00:00 2001 From: David Jeffery Date: Mon, 15 Jun 2026 13:46:30 -0400 Subject: scsi: core: wake eh reliably when using scsi_schedule_eh Drivers which use the scsi_schedule_eh function to run the error handler currently risk the error handler thread never waking once all commands are timed out or inactive. There is no enforced memory order between setting the host into error recovery state and counting busy commands. This can result in a race with scsi_dec_host_busy where neither CPU sees both conditions of all commands inactive and the host error state to request waking the error handler. To fix this, run the scsi_schedule_eh's scsi_eh_wakeup from a new work item which will use rcu to ensure scsi_schedule_eh's call to scsi_host_busy will occur after the error state is globally visible and will be seen by any current scsi_dec_host_busy callers. Fixes: 6eb045e092ef ("scsi: core: avoid host-wide host_busy counter for scsi_mq") Signed-off-by: David Jeffery Link: https://patch.msgid.link/20260615174630.11492-1-djeffery@redhat.com Signed-off-by: Martin K. Petersen --- drivers/scsi/hosts.c | 2 ++ drivers/scsi/scsi_error.c | 22 +++++++++++++++++++++- drivers/scsi/scsi_priv.h | 1 + include/scsi/scsi_host.h | 3 +++ 4 files changed, 27 insertions(+), 1 deletion(-) (limited to 'include') diff --git a/drivers/scsi/hosts.c b/drivers/scsi/hosts.c index e047747d4ecf..46cc8e3c79a2 100644 --- a/drivers/scsi/hosts.c +++ b/drivers/scsi/hosts.c @@ -357,6 +357,7 @@ static void scsi_host_dev_release(struct device *dev) /* Wait for functions invoked through call_rcu(&scmd->rcu, ...) */ rcu_barrier(); + cancel_work_sync(&shost->eh_work); if (shost->tmf_work_q) destroy_workqueue(shost->tmf_work_q); if (shost->ehandler) @@ -422,6 +423,7 @@ struct Scsi_Host *scsi_host_alloc(const struct scsi_host_template *sht, int priv INIT_LIST_HEAD(&shost->starved_list); init_waitqueue_head(&shost->host_wait); mutex_init(&shost->scan_mutex); + INIT_WORK(&shost->eh_work, scsi_rcu_eh_wakeup); index = ida_alloc(&host_index_ida, GFP_KERNEL); if (index < 0) { diff --git a/drivers/scsi/scsi_error.c b/drivers/scsi/scsi_error.c index 147127fb4db9..453a2232452d 100644 --- a/drivers/scsi/scsi_error.c +++ b/drivers/scsi/scsi_error.c @@ -73,6 +73,26 @@ void scsi_eh_wakeup(struct Scsi_Host *shost, unsigned int busy) } } +void scsi_rcu_eh_wakeup(struct work_struct *work) +{ + struct Scsi_Host *shost = container_of(work, struct Scsi_Host, eh_work); + unsigned long flags; + unsigned int busy; + + /* + * Ensure any running scsi_dec_host_busy has completed its rcu section + * so changes to host state and host_eh_scheduled are visible to all + * future calls of scsi_dec_host_busy + */ + synchronize_rcu(); + + busy = scsi_host_busy(shost); + + spin_lock_irqsave(shost->host_lock, flags); + scsi_eh_wakeup(shost, busy); + spin_unlock_irqrestore(shost->host_lock, flags); +} + /** * scsi_schedule_eh - schedule EH for SCSI host * @shost: SCSI host to invoke error handling on. @@ -88,7 +108,7 @@ void scsi_schedule_eh(struct Scsi_Host *shost) if (scsi_host_set_state(shost, SHOST_RECOVERY) == 0 || scsi_host_set_state(shost, SHOST_CANCEL_RECOVERY) == 0) { shost->host_eh_scheduled++; - scsi_eh_wakeup(shost, scsi_host_busy(shost)); + queue_work(shost->tmf_work_q, &shost->eh_work); } spin_unlock_irqrestore(shost->host_lock, flags); diff --git a/drivers/scsi/scsi_priv.h b/drivers/scsi/scsi_priv.h index 37e5601be2b8..3dbf2ca59536 100644 --- a/drivers/scsi/scsi_priv.h +++ b/drivers/scsi/scsi_priv.h @@ -91,6 +91,7 @@ extern enum blk_eh_timer_return scsi_timeout(struct request *req); extern int scsi_error_handler(void *host); extern enum scsi_disposition scsi_decide_disposition(struct scsi_cmnd *cmd); extern void scsi_eh_wakeup(struct Scsi_Host *shost, unsigned int busy); +extern void scsi_rcu_eh_wakeup(struct work_struct *work); extern void scsi_eh_scmd_add(struct scsi_cmnd *); void scsi_eh_ready_devs(struct Scsi_Host *shost, struct list_head *work_q, diff --git a/include/scsi/scsi_host.h b/include/scsi/scsi_host.h index 7e2011830ba4..f6b286fa59f2 100644 --- a/include/scsi/scsi_host.h +++ b/include/scsi/scsi_host.h @@ -750,6 +750,9 @@ struct Scsi_Host { */ struct device *dma_dev; + /* Used for an rcu-synchronizing eh wakeup */ + struct work_struct eh_work; + /* Delay for runtime autosuspend */ int rpm_autosuspend_delay; -- cgit From c1cec2bbbeb5922d42d28c6af1707c4f3f8647e3 Mon Sep 17 00:00:00 2001 From: Mikhail Gavrilov Date: Fri, 10 Jul 2026 14:47:31 +0500 Subject: Bluetooth: mgmt: Translate HCI reason in Device Disconnected event MGMT_EV_DEVICE_DISCONNECTED carries a reason field which is defined to be one of MGMT_DEV_DISCONN_* (0x00..0x05). hci_disconn_complete_evt() converts the HCI error with hci_to_mgmt_reason(), but two other paths pass the raw HCI error straight through: hci_cs_disconnect() -> cp->reason mgmt_connect_failed() -> status The latter is reached whenever the adapter is powered off or suspended: hci_disconnect_all_sync() aborts every link with HCI_ERROR_REMOTE_POWER_OFF, hci_disconnect_sync() deliberately does not wait for HCI_EV_DISCONN_COMPLETE for that reason, so that hci_abort_conn_sync() finishes the connection off through hci_conn_failed() instead. As a result userspace sees an out of range reason: @ MGMT Event: Device Disconnected (0x000c) plen 8 BR/EDR Address: 8C:A9:6F:2C:51:46 Reason: Reserved (0x15) bluetoothd: btd_bearer_disconnected() Unknown disconnection value: 21 bluetoothd: device_disconnected() Unknown disconnection value: 21 Export hci_to_mgmt_reason() and use it in both places, so that a power off is reported as MGMT_DEV_DISCONN_REMOTE rather than as the raw HCI_ERROR_REMOTE_POWER_OFF (0x15). Fixes: d47da6bd4cfa ("Bluetooth: hci_core: Fix sending MGMT_EV_CONNECT_FAILED") Fixes: 182ee45da083 ("Bluetooth: hci_sync: Rework hci_suspend_notifier") Signed-off-by: Mikhail Gavrilov Signed-off-by: Luiz Augusto von Dentz --- include/net/bluetooth/hci_core.h | 1 + net/bluetooth/hci_event.c | 18 +----------------- net/bluetooth/mgmt.c | 19 ++++++++++++++++++- 3 files changed, 20 insertions(+), 18 deletions(-) (limited to 'include') diff --git a/include/net/bluetooth/hci_core.h b/include/net/bluetooth/hci_core.h index 4ca09298e11a..e7133ff87fbf 100644 --- a/include/net/bluetooth/hci_core.h +++ b/include/net/bluetooth/hci_core.h @@ -2430,6 +2430,7 @@ void mgmt_new_link_key(struct hci_dev *hdev, struct link_key *key, bool persistent); void mgmt_device_connected(struct hci_dev *hdev, struct hci_conn *conn, u8 *name, u8 name_len); +u8 hci_to_mgmt_reason(u8 err); void mgmt_device_disconnected(struct hci_dev *hdev, bdaddr_t *bdaddr, u8 link_type, u8 addr_type, u8 reason, bool mgmt_connected); diff --git a/net/bluetooth/hci_event.c b/net/bluetooth/hci_event.c index b6d963ce26d0..741d658e9630 100644 --- a/net/bluetooth/hci_event.c +++ b/net/bluetooth/hci_event.c @@ -2763,7 +2763,7 @@ static void hci_cs_disconnect(struct hci_dev *hdev, u8 status) } mgmt_device_disconnected(hdev, &conn->dst, conn->type, conn->dst_type, - cp->reason, mgmt_conn); + hci_to_mgmt_reason(cp->reason), mgmt_conn); hci_disconn_cfm(conn, cp->reason); @@ -3381,22 +3381,6 @@ unlock: hci_dev_unlock(hdev); } -static u8 hci_to_mgmt_reason(u8 err) -{ - switch (err) { - case HCI_ERROR_CONNECTION_TIMEOUT: - return MGMT_DEV_DISCONN_TIMEOUT; - case HCI_ERROR_REMOTE_USER_TERM: - case HCI_ERROR_REMOTE_LOW_RESOURCES: - case HCI_ERROR_REMOTE_POWER_OFF: - return MGMT_DEV_DISCONN_REMOTE; - case HCI_ERROR_LOCAL_HOST_TERM: - return MGMT_DEV_DISCONN_LOCAL_HOST; - default: - return MGMT_DEV_DISCONN_UNKNOWN; - } -} - static void hci_disconn_complete_evt(struct hci_dev *hdev, void *data, struct sk_buff *skb) { diff --git a/net/bluetooth/mgmt.c b/net/bluetooth/mgmt.c index 23a1aded0ca8..1db10e0f617f 100644 --- a/net/bluetooth/mgmt.c +++ b/net/bluetooth/mgmt.c @@ -9908,6 +9908,22 @@ bool mgmt_powering_down(struct hci_dev *hdev) return false; } +u8 hci_to_mgmt_reason(u8 err) +{ + switch (err) { + case HCI_ERROR_CONNECTION_TIMEOUT: + return MGMT_DEV_DISCONN_TIMEOUT; + case HCI_ERROR_REMOTE_USER_TERM: + case HCI_ERROR_REMOTE_LOW_RESOURCES: + case HCI_ERROR_REMOTE_POWER_OFF: + return MGMT_DEV_DISCONN_REMOTE; + case HCI_ERROR_LOCAL_HOST_TERM: + return MGMT_DEV_DISCONN_LOCAL_HOST; + default: + return MGMT_DEV_DISCONN_UNKNOWN; + } +} + void mgmt_device_disconnected(struct hci_dev *hdev, bdaddr_t *bdaddr, u8 link_type, u8 addr_type, u8 reason, bool mgmt_connected) @@ -9969,7 +9985,8 @@ void mgmt_connect_failed(struct hci_dev *hdev, struct hci_conn *conn, u8 status) if (test_and_clear_bit(HCI_CONN_MGMT_CONNECTED, &conn->flags)) { mgmt_device_disconnected(hdev, &conn->dst, conn->type, - conn->dst_type, status, true); + conn->dst_type, + hci_to_mgmt_reason(status), true); return; } -- cgit From 04af4efde58a4a4ef4feab7360c46af9ec0b83a0 Mon Sep 17 00:00:00 2001 From: Mattia Meleleo Date: Wed, 8 Jul 2026 18:55:00 +0200 Subject: bpf, sockmap: Account for receive queue in FIONREAD without a verdict program tcp_bpf_ioctl() answers SIOCINQ from psock->msg_tot_len, which only counts bytes in ingress_msg. Without a stream/skb verdict program nothing is diverted there: data stays in sk_receive_queue, so FIONREAD returns 0 even though read() returns data. Add tcp_inq() to the reported value when the psock has no verdict program. The two queues are disjoint, so bytes redirected into ingress_msg from other sockets stay correctly accounted through msg_tot_len. Remove unused sk_psock_msg_inq(). Fixes: 929e30f93125 ("bpf, sockmap: Fix FIONREAD for sockmap") Signed-off-by: Mattia Meleleo Reviewed-by: Jiayuan Chen Reviewed-by: Emil Tsalapatis Reviewed-by: John Fastabend Link: https://lore.kernel.org/bpf/20260708-fionread-no-verdict-v3-1-b4ee31b3af53@coralogix.com Signed-off-by: Kumar Kartikeya Dwivedi --- include/linux/skmsg.h | 14 -------------- net/ipv4/tcp_bpf.c | 17 ++++++++++++++++- 2 files changed, 16 insertions(+), 15 deletions(-) (limited to 'include') diff --git a/include/linux/skmsg.h b/include/linux/skmsg.h index a8553401b1c9..d5e35f24738d 100644 --- a/include/linux/skmsg.h +++ b/include/linux/skmsg.h @@ -551,20 +551,6 @@ static inline void psock_progs_drop(struct sk_psock_progs *progs) psock_set_prog(&progs->skb_verdict, NULL); } -/* for tcp only, sk is locked */ -static inline ssize_t sk_psock_msg_inq(struct sock *sk) -{ - struct sk_psock *psock; - ssize_t inq = 0; - - psock = sk_psock_get(sk); - if (likely(psock)) { - inq = sk_psock_get_msg_len_nolock(psock); - sk_psock_put(sk, psock); - } - return inq; -} - /* for udp only, sk is not locked */ static inline ssize_t sk_msg_first_len(struct sock *sk) { diff --git a/net/ipv4/tcp_bpf.c b/net/ipv4/tcp_bpf.c index cc0bd73f36b6..8e905b50dead 100644 --- a/net/ipv4/tcp_bpf.c +++ b/net/ipv4/tcp_bpf.c @@ -334,6 +334,7 @@ unlock: static int tcp_bpf_ioctl(struct sock *sk, int cmd, int *karg) { + struct sk_psock *psock; bool slow; if (cmd != SIOCINQ) @@ -344,7 +345,21 @@ static int tcp_bpf_ioctl(struct sock *sk, int cmd, int *karg) return -EINVAL; slow = lock_sock_fast(sk); - *karg = sk_psock_msg_inq(sk); + psock = sk_psock_get(sk); + if (unlikely(!psock)) { + unlock_sock_fast(sk, slow); + return tcp_ioctl(sk, cmd, karg); + } + *karg = sk_psock_get_msg_len_nolock(psock); + /* Without a verdict program, ingress data is never diverted to + * ingress_msg: it stays in sk_receive_queue and is read through + * the fallback to tcp_recvmsg(), so account for it like + * tcp_ioctl() does. + */ + if (!READ_ONCE(psock->progs.stream_verdict) && + !READ_ONCE(psock->progs.skb_verdict)) + *karg += tcp_inq(sk); + sk_psock_put(sk, psock); unlock_sock_fast(sk, slow); return 0; -- cgit From d83762005c13269fffa51620399fc8d09095c4f7 Mon Sep 17 00:00:00 2001 From: Stéphane Grosjean Date: Thu, 7 May 2026 10:22:27 +0200 Subject: can: peak: Modification of references to email accounts being deleted MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Following the sale of PEAK-System France by HMS-Networks, this update is intended to change all my @hms-networks.com email addresses to my new @peak-system.fr address. Signed-off-by: Stéphane Grosjean Link: https://patch.msgid.link/20260410124251.40506-1-stephane.grosjean@free.fr Signed-off-by: Marc Kleine-Budde --- .mailmap | 4 ++-- drivers/net/can/peak_canfd/peak_canfd.c | 2 +- drivers/net/can/peak_canfd/peak_canfd_user.h | 2 +- drivers/net/can/peak_canfd/peak_pciefd_main.c | 4 ++-- drivers/net/can/sja1000/peak_pci.c | 4 ++-- drivers/net/can/sja1000/peak_pcmcia.c | 4 ++-- drivers/net/can/usb/peak_usb/pcan_usb.c | 2 +- drivers/net/can/usb/peak_usb/pcan_usb_core.c | 4 ++-- drivers/net/can/usb/peak_usb/pcan_usb_core.h | 2 +- drivers/net/can/usb/peak_usb/pcan_usb_fd.c | 2 +- drivers/net/can/usb/peak_usb/pcan_usb_pro.c | 2 +- drivers/net/can/usb/peak_usb/pcan_usb_pro.h | 2 +- include/linux/can/dev/peak_canfd.h | 2 +- 13 files changed, 18 insertions(+), 18 deletions(-) (limited to 'include') diff --git a/.mailmap b/.mailmap index 12f3acdebd72..918fcd6575b1 100644 --- a/.mailmap +++ b/.mailmap @@ -825,8 +825,8 @@ Sriram Yagnaraman Stanislav Fomichev Stanislav Fomichev Stefan Wahren -Stéphane Grosjean -Stéphane Grosjean +Stéphane Grosjean +Stéphane Grosjean Stéphane Witzmann Stephen Hemminger Stephen Hemminger diff --git a/drivers/net/can/peak_canfd/peak_canfd.c b/drivers/net/can/peak_canfd/peak_canfd.c index 06cb2629f66a..4fd1aefb780f 100644 --- a/drivers/net/can/peak_canfd/peak_canfd.c +++ b/drivers/net/can/peak_canfd/peak_canfd.c @@ -2,7 +2,7 @@ /* Copyright (C) 2007, 2011 Wolfgang Grandegger * * Copyright (C) 2016-2025 PEAK System-Technik GmbH - * Author: Stéphane Grosjean + * Author: Stéphane Grosjean */ #include diff --git a/drivers/net/can/peak_canfd/peak_canfd_user.h b/drivers/net/can/peak_canfd/peak_canfd_user.h index 60c6542028cf..dc0ecb566a85 100644 --- a/drivers/net/can/peak_canfd/peak_canfd_user.h +++ b/drivers/net/can/peak_canfd/peak_canfd_user.h @@ -2,7 +2,7 @@ /* CAN driver for PEAK System micro-CAN based adapters * * Copyright (C) 2003-2025 PEAK System-Technik GmbH - * Author: Stéphane Grosjean + * Author: Stéphane Grosjean */ #ifndef PEAK_CANFD_USER_H #define PEAK_CANFD_USER_H diff --git a/drivers/net/can/peak_canfd/peak_pciefd_main.c b/drivers/net/can/peak_canfd/peak_pciefd_main.c index 93558e33bc02..7c749301ea84 100644 --- a/drivers/net/can/peak_canfd/peak_pciefd_main.c +++ b/drivers/net/can/peak_canfd/peak_pciefd_main.c @@ -4,7 +4,7 @@ * Derived from the PCAN project file driver/src/pcan_pci.c: * * Copyright (C) 2001-2025 PEAK System-Technik GmbH - * Author: Stéphane Grosjean + * Author: Stéphane Grosjean */ #include @@ -19,7 +19,7 @@ #include "peak_canfd_user.h" -MODULE_AUTHOR("Stéphane Grosjean "); +MODULE_AUTHOR("Stéphane Grosjean "); MODULE_DESCRIPTION("Socket-CAN driver for PEAK PCAN PCIe/M.2 FD family cards"); MODULE_LICENSE("GPL v2"); diff --git a/drivers/net/can/sja1000/peak_pci.c b/drivers/net/can/sja1000/peak_pci.c index 4cc4a1581dd1..69c61ccf621d 100644 --- a/drivers/net/can/sja1000/peak_pci.c +++ b/drivers/net/can/sja1000/peak_pci.c @@ -5,7 +5,7 @@ * Derived from the PCAN project file driver/src/pcan_pci.c: * * Copyright (C) 2001-2025 PEAK System-Technik GmbH - * Author: Stéphane Grosjean + * Author: Stéphane Grosjean */ #include @@ -22,7 +22,7 @@ #include "sja1000.h" -MODULE_AUTHOR("Stéphane Grosjean "); +MODULE_AUTHOR("Stéphane Grosjean "); MODULE_DESCRIPTION("Socket-CAN driver for PEAK PCAN PCI family cards"); MODULE_LICENSE("GPL v2"); diff --git a/drivers/net/can/sja1000/peak_pcmcia.c b/drivers/net/can/sja1000/peak_pcmcia.c index 42a77d435b39..c3c2aa21da47 100644 --- a/drivers/net/can/sja1000/peak_pcmcia.c +++ b/drivers/net/can/sja1000/peak_pcmcia.c @@ -4,7 +4,7 @@ * Derived from the PCAN project file driver/src/pcan_pccard.c * * Copyright (C) 2006-2025 PEAK System-Technik GmbH - * Author: Stéphane Grosjean + * Author: Stéphane Grosjean */ #include #include @@ -19,7 +19,7 @@ #include #include "sja1000.h" -MODULE_AUTHOR("Stéphane Grosjean "); +MODULE_AUTHOR("Stéphane Grosjean "); MODULE_DESCRIPTION("CAN driver for PEAK-System PCAN-PC Cards"); MODULE_LICENSE("GPL v2"); diff --git a/drivers/net/can/usb/peak_usb/pcan_usb.c b/drivers/net/can/usb/peak_usb/pcan_usb.c index 9278a1522aae..8fd058c32856 100644 --- a/drivers/net/can/usb/peak_usb/pcan_usb.c +++ b/drivers/net/can/usb/peak_usb/pcan_usb.c @@ -4,7 +4,7 @@ * Derived from the PCAN project file driver/src/pcan_usb.c * * Copyright (C) 2003-2025 PEAK System-Technik GmbH - * Author: Stéphane Grosjean + * Author: Stéphane Grosjean * * Many thanks to Klaus Hitschler */ diff --git a/drivers/net/can/usb/peak_usb/pcan_usb_core.c b/drivers/net/can/usb/peak_usb/pcan_usb_core.c index cf48bb26d46d..c7933d1acc99 100644 --- a/drivers/net/can/usb/peak_usb/pcan_usb_core.c +++ b/drivers/net/can/usb/peak_usb/pcan_usb_core.c @@ -4,7 +4,7 @@ * Derived from the PCAN project file driver/src/pcan_usb_core.c * * Copyright (C) 2003-2025 PEAK System-Technik GmbH - * Author: Stéphane Grosjean + * Author: Stéphane Grosjean * * Many thanks to Klaus Hitschler */ @@ -24,7 +24,7 @@ #include "pcan_usb_core.h" -MODULE_AUTHOR("Stéphane Grosjean "); +MODULE_AUTHOR("Stéphane Grosjean "); MODULE_DESCRIPTION("CAN driver for PEAK-System USB adapters"); MODULE_LICENSE("GPL v2"); diff --git a/drivers/net/can/usb/peak_usb/pcan_usb_core.h b/drivers/net/can/usb/peak_usb/pcan_usb_core.h index d1c1897d47b9..65999f04f4b7 100644 --- a/drivers/net/can/usb/peak_usb/pcan_usb_core.h +++ b/drivers/net/can/usb/peak_usb/pcan_usb_core.h @@ -4,7 +4,7 @@ * Derived from the PCAN project file driver/src/pcan_usb_core.c * * Copyright (C) 2003-2025 PEAK System-Technik GmbH - * Author: Stéphane Grosjean + * Author: Stéphane Grosjean * * Many thanks to Klaus Hitschler */ diff --git a/drivers/net/can/usb/peak_usb/pcan_usb_fd.c b/drivers/net/can/usb/peak_usb/pcan_usb_fd.c index eb4f5884ad73..ef9fd693e9bd 100644 --- a/drivers/net/can/usb/peak_usb/pcan_usb_fd.c +++ b/drivers/net/can/usb/peak_usb/pcan_usb_fd.c @@ -3,7 +3,7 @@ * CAN driver for PEAK System PCAN-USB FD / PCAN-USB Pro FD adapter * * Copyright (C) 2013-2025 PEAK System-Technik GmbH - * Author: Stéphane Grosjean + * Author: Stéphane Grosjean */ #include #include diff --git a/drivers/net/can/usb/peak_usb/pcan_usb_pro.c b/drivers/net/can/usb/peak_usb/pcan_usb_pro.c index 4bfa8d0fbb32..aefcded8e12a 100644 --- a/drivers/net/can/usb/peak_usb/pcan_usb_pro.c +++ b/drivers/net/can/usb/peak_usb/pcan_usb_pro.c @@ -4,7 +4,7 @@ * Derived from the PCAN project file driver/src/pcan_usbpro.c * * Copyright (C) 2003-2025 PEAK System-Technik GmbH - * Author: Stéphane Grosjean + * Author: Stéphane Grosjean */ #include #include diff --git a/drivers/net/can/usb/peak_usb/pcan_usb_pro.h b/drivers/net/can/usb/peak_usb/pcan_usb_pro.h index 162c7546d3a8..d669c9e610c7 100644 --- a/drivers/net/can/usb/peak_usb/pcan_usb_pro.h +++ b/drivers/net/can/usb/peak_usb/pcan_usb_pro.h @@ -4,7 +4,7 @@ * Derived from the PCAN project file driver/src/pcan_usbpro_fw.h * * Copyright (C) 2003-2025 PEAK System-Technik GmbH - * Author: Stéphane Grosjean + * Author: Stéphane Grosjean */ #ifndef PCAN_USB_PRO_H #define PCAN_USB_PRO_H diff --git a/include/linux/can/dev/peak_canfd.h b/include/linux/can/dev/peak_canfd.h index d3788a3d0942..056e0efa649f 100644 --- a/include/linux/can/dev/peak_canfd.h +++ b/include/linux/can/dev/peak_canfd.h @@ -3,7 +3,7 @@ * CAN driver for PEAK System micro-CAN based adapters * * Copyright (C) 2003-2025 PEAK System-Technik GmbH - * Author: Stéphane Grosjean + * Author: Stéphane Grosjean */ #ifndef PUCAN_H #define PUCAN_H -- cgit From 3917b1012ee2fef6da16d7450d4267dcb3e93363 Mon Sep 17 00:00:00 2001 From: Ihor Solodrai Date: Mon, 13 Jul 2026 16:52:22 -0700 Subject: bpf: Fix tracing of kfuncs with implicit args A kfunc marked with KF_IMPLICIT_ARGS flag takes implicit arguments (such as bpf_prog_aux) that the verifier injects at load time. resolve_btfids strips those from the kfunc's BTF-visible prototype and keeps the real kernel ABI in a counterpart _impl prototype [1]. fentry/fexit/fmod_ret/fsession programs may attach to the BPF kernel functions, including those with implicit args. However bpf_check_attach_target() and bpf_check_attach_btf_id_multi() extract the struct btf_func_model from the wrong BTF prototype of the kfunc. The btf_func_model is later read to construct the trampoline, which then causes the injected implicit argument to be clobbered and the kfunc dereferencing garbage. Add btf_attach_func_proto() to resolve the real ABI prototype of the kfunc the way the call site does: by looking up the _impl prototype for a KF_IMPLICIT_ARGS kfunc. Use it at both attach-target model construction sites. To enable this, make two supporting changes: * pass bpf_verifier_log instead of bpf_verifier_env to find_kfunc_impl_proto(), so it can be reused from the attach path * add btf_kfunc_check_flag() to test a flag across all of a kfunc's hook sets, because a program attaching to a kfunc is not in the kfunc's call-set KF_IMPLICIT_ARGS must be consistent across the sets, so btf_kfunc_check_flag() returns -EINVAL on inconsistency. btf_kfunc_check_flag() reads the kfunc's flags from the target's kfunc_set_tab. For a module BTF that table is stable only after the module is live, so take a module reference around the read, mirroring how the kfunc call path gates the same lookup with btf_try_get_module(). The remaining call sites of btf_distill_func_proto() are safe as is. The BPF_TRACE_ITER case distills a registered iterator's prototype, and bpf_struct_ops_desc_init() distills the function-pointer members of a struct_ops type. Neither is a kfunc, and so can't have implicit arguments. [1] https://lore.kernel.org/all/20260120222638.3976562-1-ihor.solodrai@linux.dev/ Fixes: 64e1360524b9 ("bpf: Verifier support for KF_IMPLICIT_ARGS") Reported-by: Tejun Heo Signed-off-by: Ihor Solodrai Link: https://github.com/sched-ext/scx/issues/3687#issuecomment-4906694106 Link: https://patch.msgid.link/20260713235223.1639022-2-ihor.solodrai@linux.dev Signed-off-by: Eduard Zingerman --- include/linux/btf.h | 1 + kernel/bpf/btf.c | 29 ++++++++++++++++++++++ kernel/bpf/verifier.c | 66 +++++++++++++++++++++++++++++++++++++++++---------- 3 files changed, 83 insertions(+), 13 deletions(-) (limited to 'include') diff --git a/include/linux/btf.h b/include/linux/btf.h index 240401d9b25b..c09b7994de4e 100644 --- a/include/linux/btf.h +++ b/include/linux/btf.h @@ -578,6 +578,7 @@ const char *btf_str_by_offset(const struct btf *btf, u32 offset); struct btf *btf_parse_vmlinux(void); struct btf *bpf_prog_get_target_btf(const struct bpf_prog *prog); u32 *btf_kfunc_flags(const struct btf *btf, u32 kfunc_btf_id, const struct bpf_prog *prog); +int btf_kfunc_check_flag(const struct btf *btf, u32 kfunc_btf_id, u32 flag); bool btf_kfunc_is_allowed(const struct btf *btf, u32 kfunc_btf_id, const struct bpf_prog *prog); u32 *btf_kfunc_is_modify_return(const struct btf *btf, u32 kfunc_btf_id, const struct bpf_prog *prog); diff --git a/kernel/bpf/btf.c b/kernel/bpf/btf.c index 64572f85edc8..c4673a54c4ba 100644 --- a/kernel/bpf/btf.c +++ b/kernel/bpf/btf.c @@ -9114,6 +9114,35 @@ u32 *btf_kfunc_flags(const struct btf *btf, u32 kfunc_btf_id, const struct bpf_p return btf_kfunc_id_set_contains(btf, hook, kfunc_btf_id); } +/* + * Check a single KF_* @flag on a kfunc across all of its hook sets. + * Returns: + * * 1 if @flag is set + * * 0 if @flag is not set + * * -EINVAL if @flag is set inconsistently across the sets + * * -ENOENT if kfunc_btf_id is not a registered kfunc + */ +int btf_kfunc_check_flag(const struct btf *btf, u32 kfunc_btf_id, u32 flag) +{ + enum btf_kfunc_hook hook; + int res = -ENOENT; + bool is_set; + u32 *flags; + + for (hook = 0; hook < BTF_KFUNC_HOOK_MAX; hook++) { + flags = btf_kfunc_id_set_contains(btf, hook, kfunc_btf_id); + if (!flags) + continue; + is_set = *flags & flag; + if (res < 0) + res = is_set; + else if (res != is_set) + return -EINVAL; + } + + return res; +} + u32 *btf_kfunc_is_modify_return(const struct btf *btf, u32 kfunc_btf_id, const struct bpf_prog *prog) { diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c index 9f1333676365..99444eae917e 100644 --- a/kernel/bpf/verifier.c +++ b/kernel/bpf/verifier.c @@ -2584,24 +2584,25 @@ static struct btf *find_kfunc_desc_btf(struct bpf_verifier_env *env, s16 offset) #define KF_IMPL_SUFFIX "_impl" -static const struct btf_type *find_kfunc_impl_proto(struct bpf_verifier_env *env, +static const struct btf_type *find_kfunc_impl_proto(struct bpf_verifier_log *log, struct btf *btf, const char *func_name) { - char *buf = env->tmp_str_buf; const struct btf_type *func; + char buf[KSYM_NAME_LEN]; s32 impl_id; int len; - len = snprintf(buf, TMP_STR_BUF_LEN, "%s%s", func_name, KF_IMPL_SUFFIX); - if (len < 0 || len >= TMP_STR_BUF_LEN) { - verbose(env, "function name %s%s is too long\n", func_name, KF_IMPL_SUFFIX); + len = snprintf(buf, sizeof(buf), "%s%s", func_name, KF_IMPL_SUFFIX); + if (len < 0 || len >= sizeof(buf)) { + bpf_log(log, "function name %s%s is too long\n", + func_name, KF_IMPL_SUFFIX); return NULL; } impl_id = btf_find_by_name_kind(btf, buf, BTF_KIND_FUNC); if (impl_id <= 0) { - verbose(env, "cannot find function %s in BTF\n", buf); + bpf_log(log, "cannot find function %s in BTF\n", buf); return NULL; } @@ -2653,7 +2654,7 @@ static int fetch_kfunc_meta(struct bpf_verifier_env *env, * can be found through the counterpart _impl kfunc. */ if (kfunc_flags && (*kfunc_flags & KF_IMPLICIT_ARGS)) - func_proto = find_kfunc_impl_proto(env, btf, func_name); + func_proto = find_kfunc_impl_proto(&env->log, btf, func_name); else func_proto = btf_type_by_id(btf, func->type); @@ -18880,6 +18881,47 @@ static int btf_id_allow_sleepable(u32 btf_id, unsigned long addr, const struct b return -EINVAL; } +/* + * Resolve the prototype describing a trace target's real ABI. A + * KF_IMPLICIT_ARGS kfunc has its injected args stripped from the public + * prototype, so use the _impl prototype; other targets use their own. + */ +static const struct btf_type * +btf_attach_func_proto(struct bpf_verifier_log *log, struct btf *btf, u32 func_id) +{ + const struct btf_type *func; + struct module *mod = NULL; + const char *name; + int implicit; + + func = btf_type_by_id(btf, func_id); + if (!func || !btf_type_is_func(func)) + return NULL; + name = btf_name_by_offset(btf, func->name_off); + + /* + * btf_kfunc_check_flag() reads kfunc_set_tab, which for a module is + * stable only once it is live; hold a module ref across the read to + * exclude a concurrent module load. + */ + if (btf_is_module(btf)) { + mod = btf_try_get_module(btf); + if (!mod) + return NULL; + } + implicit = btf_kfunc_check_flag(btf, func_id, KF_IMPLICIT_ARGS); + module_put(mod); + + if (implicit == -EINVAL) { + bpf_log(log, "kfunc %s has inconsistent KF_IMPLICIT_ARGS\n", name); + return NULL; + } + if (implicit > 0) + return find_kfunc_impl_proto(log, btf, name); + + return btf_type_by_id(btf, func->type); +} + int bpf_check_attach_target(struct bpf_verifier_log *log, const struct bpf_prog *prog, const struct bpf_prog *tgt_prog, @@ -19128,8 +19170,8 @@ int bpf_check_attach_target(struct bpf_verifier_log *log, if (prog_extension && btf_check_type_match(log, prog, btf, t)) return -EINVAL; - t = btf_type_by_id(btf, t->type); - if (!btf_type_is_func_proto(t)) + t = btf_attach_func_proto(log, btf, btf_id); + if (!t || !btf_type_is_func_proto(t)) return -EINVAL; if ((prog->aux->saved_dst_prog_type || prog->aux->saved_dst_attach_type) && @@ -19412,10 +19454,8 @@ int bpf_check_attach_btf_id_multi(struct btf *btf, struct bpf_prog *prog, u32 bt tname = btf_name_by_offset(btf, t->name_off); if (!tname) return -EINVAL; - if (!btf_type_is_func(t)) - return -EINVAL; - t = btf_type_by_id(btf, t->type); - if (!btf_type_is_func_proto(t)) + t = btf_attach_func_proto(NULL, btf, btf_id); + if (!t || !btf_type_is_func_proto(t)) return -EINVAL; err = btf_distill_func_proto(NULL, btf, t, tname, &tgt_info->fmodel); if (err < 0) -- cgit From e0b5252a59383b77d1b8dbeda00b7184dd95f4d3 Mon Sep 17 00:00:00 2001 From: Xin Long Date: Fri, 10 Jul 2026 14:12:35 -0400 Subject: sctp: fix auth_hmacs array size in struct sctp_cookie The auth_hmacs array in struct sctp_cookie is supposed to store a complete SCTP_AUTH_HMAC_ALGO parameter, which consists of a struct sctp_paramhdr followed by N HMAC identifiers. However, the array size was calculated using an extra 2 bytes instead of sizeof(struct sctp_paramhdr), which is 4 bytes. When four HMAC identifiers are configured, the HMAC-ALGO parameter stored in the endpoint is larger than the auth_hmacs buffer in the cookie. As a result, sctp_association_init() copies beyond the end of auth_hmacs when initializing the association, corrupting the adjacent auth_chunks field. This can lead to an invalid HMAC identifier being accepted and later cause an out-of-bounds read in sctp_auth_get_hmac(). Fix the array size calculation by including the full SCTP parameter header size. Fixes: 1f485649f529 ("[SCTP]: Implement SCTP-AUTH internals") Reported-by: Yuan Tan Reported-by: Xin Liu Reported-by: Zihan Xi Reported-by: Ren Wei Signed-off-by: Xin Long Link: https://patch.msgid.link/634a0de0d5de29532915e6d47c92a0cbc206e03f.1783707155.git.lucien.xin@gmail.com Signed-off-by: Paolo Abeni --- include/net/sctp/structs.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'include') diff --git a/include/net/sctp/structs.h b/include/net/sctp/structs.h index affee44bd38e..cccc662561aa 100644 --- a/include/net/sctp/structs.h +++ b/include/net/sctp/structs.h @@ -312,7 +312,8 @@ struct sctp_cookie { __u8 auth_random[sizeof(struct sctp_paramhdr) + SCTP_AUTH_RANDOM_LENGTH]; - __u8 auth_hmacs[SCTP_AUTH_NUM_HMACS * sizeof(__u16) + 2]; + __u8 auth_hmacs[sizeof(struct sctp_paramhdr) + + SCTP_AUTH_NUM_HMACS * sizeof(__u16)]; __u8 auth_chunks[sizeof(struct sctp_paramhdr) + SCTP_AUTH_MAX_CHUNKS]; /* This is a shim for my peer's INIT packet, followed by -- cgit From bad177fa75e607e396cd57daaaed881450d7a471 Mon Sep 17 00:00:00 2001 From: Alessandro Rinaldi Date: Fri, 26 Jun 2026 16:36:00 +0200 Subject: drm/amd/display: Force PWM backlight on Lenovo Legion 5 15ARH05 The Lenovo Legion 5 15ARH05 (Renoir) ships a BOE 0x08DF eDP panel that advertises AUX/DPCD backlight control, so amdgpu's automatic detection (amdgpu_backlight == -1) selects AUX. On this panel the AUX backlight path has no effect: brightness writes are accepted but the panel level never changes, the display is stuck at a fixed brightness and max_brightness is reported as a bogus 511000. As a result neither the desktop brightness slider nor the brightness hotkeys do anything. Forcing PWM backlight (amdgpu.backlight=0) restores working control: max_brightness becomes 65535 and the level tracks writes. This has long been applied by users as a manual kernel-parameter workaround. Extend the generic panel backlight quirk with a force_pwm flag, add an entry for the Legion 5 15ARH05 / BOE 0x08DF panel, and have amdgpu disable AUX backlight (use PWM) when the quirk matches and the user lets the driver auto-select the backlight type. Signed-off-by: Alessandro Rinaldi Tested-by: Alessandro Rinaldi Reviewed-by: George Zhang Signed-off-by: Alex Deucher (cherry picked from commit 81b39f43e7e53589491e2eef6bad5389626b4b9c) Cc: stable@vger.kernel.org --- drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c | 7 +++++-- drivers/gpu/drm/drm_panel_backlight_quirks.c | 9 +++++++++ include/drm/drm_utils.h | 1 + 3 files changed, 15 insertions(+), 2 deletions(-) (limited to 'include') diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c index fe6eea28d119..6210a1ff7cc2 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c @@ -4069,6 +4069,8 @@ static void update_connector_ext_caps(struct amdgpu_dm_connector *aconnector) caps->ext_caps = &aconnector->dc_link->dpcd_sink_ext_caps; caps->aux_support = false; + panel_backlight_quirk = drm_get_panel_backlight_quirk(aconnector->drm_edid); + if (caps->ext_caps->bits.oled == 1 /* * || @@ -4081,6 +4083,9 @@ static void update_connector_ext_caps(struct amdgpu_dm_connector *aconnector) caps->aux_support = false; else if (amdgpu_backlight == 1) caps->aux_support = true; + 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; @@ -4096,8 +4101,6 @@ static void update_connector_ext_caps(struct amdgpu_dm_connector *aconnector) else caps->aux_min_input_signal = 1; - panel_backlight_quirk = - drm_get_panel_backlight_quirk(aconnector->drm_edid); if (!IS_ERR_OR_NULL(panel_backlight_quirk)) { if (panel_backlight_quirk->min_brightness) { caps->min_input_signal = diff --git a/drivers/gpu/drm/drm_panel_backlight_quirks.c b/drivers/gpu/drm/drm_panel_backlight_quirks.c index f85cb293a3db..e417c7533053 100644 --- a/drivers/gpu/drm/drm_panel_backlight_quirks.c +++ b/drivers/gpu/drm/drm_panel_backlight_quirks.c @@ -20,6 +20,15 @@ struct drm_get_panel_backlight_quirk { }; static const struct drm_get_panel_backlight_quirk drm_panel_min_backlight_quirks[] = { + /* Lenovo Legion 5 15ARH05, AUX backlight non-functional, force PWM */ + { + .dmi_match.field = DMI_SYS_VENDOR, + .dmi_match.value = "LENOVO", + .dmi_match_other.field = DMI_PRODUCT_VERSION, + .dmi_match_other.value = "Lenovo Legion 5 15ARH05", + .ident.panel_id = drm_edid_encode_panel_id('B', 'O', 'E', 0x08df), + .quirk = { .force_pwm = true, }, + }, /* 13 inch matte panel */ { .dmi_match.field = DMI_BOARD_VENDOR, diff --git a/include/drm/drm_utils.h b/include/drm/drm_utils.h index 6a46f755daba..7e077484c5bb 100644 --- a/include/drm/drm_utils.h +++ b/include/drm/drm_utils.h @@ -19,6 +19,7 @@ int drm_get_panel_orientation_quirk(int width, int height); struct drm_panel_backlight_quirk { u16 min_brightness; u32 brightness_mask; + bool force_pwm; }; const struct drm_panel_backlight_quirk * -- cgit From 7fa44519a2a3a9da715669a7f359f08e49911e2f Mon Sep 17 00:00:00 2001 From: Marco Giunta Date: Fri, 17 Jul 2026 15:25:03 +0200 Subject: ASoC: aw88399: extract shared device library Extract the device-level functions from aw88399.c into a new shared library module (aw88399-lib.c) with a shared header at include/sound/aw88399.h, following the pattern established by CS35L41 (cs35l41-lib.c / include/sound/cs35l41.h) for chips that need both ASoC and HDA drivers. The shared header at include/sound/aw88399.h contains the register definitions, bit-field masks, hardware constants, device enums, the struct aw88399 definition, and the library function declarations. The ASoC-private header at sound/soc/codecs/aw88399.h is reduced to ASoC-specific definitions (PCM formats/rates, ALSA kcontrol helpers, calibration constants) and includes the shared header. The library contains the chip initialization, firmware loading, playback start/stop sequences, and all their internal dependencies (PLL checks, DSP management, volume control, calibration, CRC verification, etc.). The ASoC codec driver retains the ALSA controls, DAPM widgets, codec probe/remove, calibration service, and I2C bus driver registration. A new Kconfig symbol SND_SOC_AW88399_LIB is introduced. SND_SOC_AW88399 (the existing ASoC codec) selects it, ensuring no change for current users. The HDA side codec driver (introduced later in this series) selects the library without pulling in the full ASoC codec module. This avoids a build-time dependency on the full ASoC driver and follows the established pattern used by CS35L41 (SND_SOC_CS35L41_LIB) for chips with both ASoC and HDA drivers. Some library functions (DSP control, volume setting, mute, calibration updates, profile management, and status helpers) are used internally by the library's start/stop sequences but are also called directly by the ASoC driver's remaining code. These are exported from the library so the ASoC module can access them. This is a pure code movement with no functional changes. The moved functions are identical to their originals in aw88399.c. Tested-by: Nadim Kobeissi Tested-by: Xia Yun'an Tested-by: Munzir Taha Signed-off-by: Marco Giunta Link: https://patch.msgid.link/DS7PR19MB772415C485FAF74297673FD7FCC62@DS7PR19MB7724.namprd19.prod.outlook.com Signed-off-by: Mark Brown --- include/sound/aw88399.h | 618 ++++++++++++++++++ sound/soc/codecs/Kconfig | 5 + sound/soc/codecs/Makefile | 2 + sound/soc/codecs/aw88399-lib.c | 1381 ++++++++++++++++++++++++++++++++++++++++ sound/soc/codecs/aw88399.c | 1349 --------------------------------------- sound/soc/codecs/aw88399.h | 582 +---------------- 6 files changed, 2008 insertions(+), 1929 deletions(-) create mode 100644 include/sound/aw88399.h create mode 100644 sound/soc/codecs/aw88399-lib.c (limited to 'include') diff --git a/include/sound/aw88399.h b/include/sound/aw88399.h new file mode 100644 index 000000000000..3a2153f0ee92 --- /dev/null +++ b/include/sound/aw88399.h @@ -0,0 +1,618 @@ +/* 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; +}; + +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_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/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index 252f683be3c1..18e34899566e 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -726,6 +726,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 +737,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 diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index aa0396e5b575..d2a689006d69 100644 --- 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 @@ -496,6 +497,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 diff --git a/sound/soc/codecs/aw88399-lib.c b/sound/soc/codecs/aw88399-lib.c new file mode 100644 index 000000000000..258d6efbf590 --- /dev/null +++ b/sound/soc/codecs/aw88399-lib.c @@ -0,0 +1,1381 @@ +// 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 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__); +} + +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(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; +} + +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_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); +} +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 = 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; +} +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; + + of_property_read_u32(np, "awinic,audio-channel", &channel_value); + 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); + +MODULE_DESCRIPTION("AW88399 common device library"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/aw88399.c b/sound/soc/codecs/aw88399.c index b2ec3503f7e2..13495e71ad0e 100644 --- a/sound/soc/codecs/aw88399.c +++ b/sound/soc/codecs/aw88399.c @@ -10,1217 +10,12 @@ #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", @@ -1869,86 +664,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, @@ -2040,70 +755,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 index b386f4836748..04123bf0ad84 100644 --- 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__ */ -- cgit From b4530a3e4895abfc308e2b45c0ea508f4dddd9f1 Mon Sep 17 00:00:00 2001 From: Marco Giunta Date: Fri, 17 Jul 2026 15:25:05 +0200 Subject: ASoC: aw88399: add per-instance BSTS status bypass flag Add a bsts_unreliable flag to struct aw88399 that, when set, causes the startup status check (aw_dev_check_sysst) to skip the BSTS (boost startup finished) requirement. On some hardware, the BSTS bit in the SYSST register (0x01, bit 9) does not reliably assert even during normal audio playback. Register inspection on affected Lenovo Legion hardware shows both amplifiers reporting BSTS=0 on both channels despite clean audio output. Per the AW88399 datasheet, BSTS indicates boost startup completion. If BSTS never reliably sets to 1, the chip is never allowed to start by aw_dev_check_sysst, regardless of whether the boot failure is genuine. The new flag defaults to false via kzalloc, preserving the original check behavior for all existing users. No existing code path sets this flag; it will be set by the forthcoming HDA side codec property driver for affected hardware. Tested-by: Nadim Kobeissi Tested-by: Xia Yun'an Tested-by: Munzir Taha Signed-off-by: Marco Giunta Link: https://patch.msgid.link/DS7PR19MB77242B8E5BB8BFB5E69816E9FCC62@DS7PR19MB7724.namprd19.prod.outlook.com Signed-off-by: Mark Brown --- include/sound/aw88399.h | 1 + sound/soc/codecs/aw88399-lib.c | 13 +++++++++++-- 2 files changed, 12 insertions(+), 2 deletions(-) (limited to 'include') diff --git a/include/sound/aw88399.h b/include/sound/aw88399.h index 3a2153f0ee92..dee91b540b81 100644 --- a/include/sound/aw88399.h +++ b/include/sound/aw88399.h @@ -598,6 +598,7 @@ struct aw88399 { unsigned int crc_init_val; unsigned int vcalb_init_val; unsigned int dither_st; + bool bsts_unreliable; }; int aw_dev_check_syspll(struct aw_device *aw_dev); diff --git a/sound/soc/codecs/aw88399-lib.c b/sound/soc/codecs/aw88399-lib.c index b525695c96d1..2045c4171be0 100644 --- a/sound/soc/codecs/aw88399-lib.c +++ b/sound/soc/codecs/aw88399-lib.c @@ -167,8 +167,9 @@ int aw_dev_check_syspll(struct aw_device *aw_dev) } EXPORT_SYMBOL_GPL(aw_dev_check_syspll); -static int aw_dev_check_sysst(struct aw_device *aw_dev) +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; @@ -182,6 +183,14 @@ static int aw_dev_check_sysst(struct aw_device *aw_dev) 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) @@ -710,7 +719,7 @@ static int aw88399_dev_start(struct aw88399 *aw88399) usleep_range(AW88399_1000_US, AW88399_1000_US + 50); /* check i2s status */ - ret = aw_dev_check_sysst(aw_dev); + ret = aw_dev_check_sysst(aw88399); if (ret) { dev_err(aw_dev->dev, "sysst check failed"); goto sysst_check_fail; -- cgit From b5e1d685fb9c9acef5942794a8dc07580c607e66 Mon Sep 17 00:00:00 2001 From: Marco Giunta Date: Fri, 17 Jul 2026 15:25:06 +0200 Subject: ASoC: aw88399: add firmware reload flag for resume Add a fw_needs_reload flag to struct aw88399 that, when set, causes aw88399_start to perform a full DSP firmware upload instead of assuming the firmware binary is already present in memory. After system sleep, the AW88399 loses its memory contents. The existing start sequence assumes the firmware binary persists from initialization and only uploads register configuration and DSP config (AW88399_DSP_FW_UPDATE_OFF). When memory is empty, this causes the subsequent CRC check to fail, triggering the retry mechanism in aw88399_start_pa which re-uploads the firmware on the second attempt. While the retry mechanism recovers correctly, it produces misleading error-level log messages on every resume cycle. The fw_needs_reload flag allows the HDA side codec driver to signal that a full firmware reload is needed after resume, eliminating the spurious CRC failures. The flag defaults to false via kzalloc, preserving the original behavior for existing ASoC users. No existing code path sets this flag; it will be set by the HDA side codec driver's system suspend handler. Tested-by: Nadim Kobeissi Tested-by: Xia Yun'an Tested-by: Munzir Taha Signed-off-by: Marco Giunta Link: https://patch.msgid.link/DS7PR19MB77240CB79188C0B7AE243829FCC62@DS7PR19MB7724.namprd19.prod.outlook.com Signed-off-by: Mark Brown --- include/sound/aw88399.h | 1 + sound/soc/codecs/aw88399-lib.c | 5 ++++- 2 files changed, 5 insertions(+), 1 deletion(-) (limited to 'include') diff --git a/include/sound/aw88399.h b/include/sound/aw88399.h index dee91b540b81..3dbc37dc13d1 100644 --- a/include/sound/aw88399.h +++ b/include/sound/aw88399.h @@ -599,6 +599,7 @@ struct aw88399 { 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); diff --git a/sound/soc/codecs/aw88399-lib.c b/sound/soc/codecs/aw88399-lib.c index 2045c4171be0..094a37b66fb5 100644 --- a/sound/soc/codecs/aw88399-lib.c +++ b/sound/soc/codecs/aw88399-lib.c @@ -1171,12 +1171,15 @@ void aw88399_start(struct aw88399 *aw88399, bool sync_start) if (aw88399->aw_pa->status == AW88399_DEV_PW_ON) return; - ret = aw88399_dev_fw_update(aw88399, AW88399_DSP_FW_UPDATE_OFF, true); + 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 -- cgit From 15c9fdb3c39d5073463ffa746cc64ad9682c875f Mon Sep 17 00:00:00 2001 From: Marco Giunta Date: Fri, 17 Jul 2026 15:25:07 +0200 Subject: ASoC: aw88399: add channel setter for HDA side codec Add aw88399_dev_set_channel() to the shared library so that the HDA side codec driver can set the amplifier's channel assignment without including the aw88395 device header directly. The AW88399's struct aw_device is defined in aw88395_device.h, which lives under sound/soc/codecs/aw88395/. Without this accessor, the HDA driver would need a cross-subsystem relative include path to access the channel field. Providing a setter in the library keeps the interface clean and avoids coupling the HDA driver to ASoC-internal headers. Tested-by: Nadim Kobeissi Tested-by: Xia Yun'an Tested-by: Munzir Taha Signed-off-by: Marco Giunta Link: https://patch.msgid.link/DS7PR19MB7724E8A1AD36D1E623FA2A0AFCC62@DS7PR19MB7724.namprd19.prod.outlook.com Signed-off-by: Mark Brown --- include/sound/aw88399.h | 1 + sound/soc/codecs/aw88399-lib.c | 6 ++++++ 2 files changed, 7 insertions(+) (limited to 'include') diff --git a/include/sound/aw88399.h b/include/sound/aw88399.h index 3dbc37dc13d1..a1e0de6be8ea 100644 --- a/include/sound/aw88399.h +++ b/include/sound/aw88399.h @@ -609,6 +609,7 @@ 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; diff --git a/sound/soc/codecs/aw88399-lib.c b/sound/soc/codecs/aw88399-lib.c index 094a37b66fb5..5c7982891def 100644 --- a/sound/soc/codecs/aw88399-lib.c +++ b/sound/soc/codecs/aw88399-lib.c @@ -1401,5 +1401,11 @@ int aw88399_init(struct aw88399 *aw88399, struct i2c_client *i2c, struct regmap } 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"); -- cgit