// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause)// Copyright(c) 2015-17 Intel Corporation. /* * Soundwire Intel Master Driver */ #include <linux/acpi.h>#include <linux/debugfs.h>#include <linux/delay.h>#include <linux/module.h>#include <linux/interrupt.h>#include <linux/io.h>#include <linux/platform_device.h>#include <sound/pcm_params.h>#include <linux/pm_runtime.h>#include <sound/soc.h>#include <linux/soundwire/sdw_registers.h>#include <linux/soundwire/sdw.h>#include <linux/soundwire/sdw_intel.h>#include "cadence_master.h"#include "bus.h"#include "intel.h" #define INTEL_MASTER_SUSPEND_DELAY_MS 3000 /* * debug/config flags for the Intel SoundWire Master. * * Since we may have multiple masters active, we can have up to 8 * flags reused in each byte, with master0 using the ls-byte, etc. */ #define SDW_INTEL_MASTER_DISABLE_PM_RUNTIME BIT(0)#define SDW_INTEL_MASTER_DISABLE_CLOCK_STOP BIT(1)#define SDW_INTEL_MASTER_DISABLE_PM_RUNTIME_IDLE BIT(2)#define SDW_INTEL_MASTER_DISABLE_MULTI_LINK BIT(3) static int md_flags;module_param_named(sdw_md_flags, md_flags, int, 0444);MODULE_PARM_DESC(sdw_md_flags, "SoundWire Intel Master device flags (0x0 all off)"); /* Intel SHIM Registers Definition */#define SDW_SHIM_LCAP 0x0#define SDW_SHIM_LCTL 0x4#define SDW_SHIM_IPPTR 0x8#define SDW_SHIM_SYNC 0xC #define SDW_SHIM_CTLSCAP(x) (0x010 + 0x60 * (x))#define SDW_SHIM_CTLS0CM(x) (0x012 + 0x60 * (x))#define SDW_SHIM_CTLS1CM(x) (0x014 + 0x60 * (x))#define SDW_SHIM_CTLS2CM(x) (0x016 + 0x60 * (x))#define SDW_SHIM_CTLS3CM(x) (0x018 + 0x60 * (x))#define SDW_SHIM_PCMSCAP(x) (0x020 + 0x60 * (x)) #define SDW_SHIM_PCMSYCHM(x, y) (0x022 + (0x60 * (x)) + (0x2 * (y)))#define SDW_SHIM_PCMSYCHC(x, y) (0x042 + (0x60 * (x)) + (0x2 * (y)))#define SDW_SHIM_PDMSCAP(x) (0x062 + 0x60 * (x))#define SDW_SHIM_IOCTL(x) (0x06C + 0x60 * (x))#define SDW_SHIM_CTMCTL(x) (0x06E + 0x60 * (x)) #define SDW_SHIM_WAKEEN 0x190#define SDW_SHIM_WAKESTS 0x192 #define SDW_SHIM_LCTL_SPA BIT(0)#define SDW_SHIM_LCTL_SPA_MASK GENMASK(3, 0)#define SDW_SHIM_LCTL_CPA BIT(8)#define SDW_SHIM_LCTL_CPA_MASK GENMASK(11, 8) #define SDW_SHIM_SYNC_SYNCPRD_VAL_24 (24000 / SDW_CADENCE_GSYNC_KHZ - 1)#define SDW_SHIM_SYNC_SYNCPRD_VAL_38_4 (38400 / SDW_CADENCE_GSYNC_KHZ - 1)#define SDW_SHIM_SYNC_SYNCPRD GENMASK(14, 0)#define SDW_SHIM_SYNC_SYNCCPU BIT(15)#define SDW_SHIM_SYNC_CMDSYNC_MASK GENMASK(19, 16)#define SDW_SHIM_SYNC_CMDSYNC BIT(16)#define SDW_SHIM_SYNC_SYNCGO BIT(24) #define SDW_SHIM_PCMSCAP_ISS GENMASK(3, 0)#define SDW_SHIM_PCMSCAP_OSS GENMASK(7, 4)#define SDW_SHIM_PCMSCAP_BSS GENMASK(12, 8) #define SDW_SHIM_PCMSYCM_LCHN GENMASK(3, 0)#define SDW_SHIM_PCMSYCM_HCHN GENMASK(7, 4)#define SDW_SHIM_PCMSYCM_STREAM GENMASK(13, 8)#define SDW_SHIM_PCMSYCM_DIR BIT(15) #define SDW_SHIM_PDMSCAP_ISS GENMASK(3, 0)#define SDW_SHIM_PDMSCAP_OSS GENMASK(7, 4)#define SDW_SHIM_PDMSCAP_BSS GENMASK(12, 8)#define SDW_SHIM_PDMSCAP_CPSS GENMASK(15, 13) #define SDW_SHIM_IOCTL_MIF BIT(0)#define SDW_SHIM_IOCTL_CO BIT(1)#define SDW_SHIM_IOCTL_COE BIT(2)#define SDW_SHIM_IOCTL_DO BIT(3)#define SDW_SHIM_IOCTL_DOE BIT(4)#define SDW_SHIM_IOCTL_BKE BIT(5)#define SDW_SHIM_IOCTL_WPDD BIT(6)#define SDW_SHIM_IOCTL_CIBD BIT(8)#define SDW_SHIM_IOCTL_DIBD BIT(9) #define SDW_SHIM_CTMCTL_DACTQE BIT(0)#define SDW_SHIM_CTMCTL_DODS BIT(1)#define SDW_SHIM_CTMCTL_DOAIS GENMASK(4, 3) #define SDW_SHIM_WAKEEN_ENABLE BIT(0)#define SDW_SHIM_WAKESTS_STATUS BIT(0) /* Intel ALH Register definitions */#define SDW_ALH_STRMZCFG(x) (0x000 + (0x4 * (x)))#define SDW_ALH_NUM_STREAMS 64 #define SDW_ALH_STRMZCFG_DMAT_VAL 0x3#define SDW_ALH_STRMZCFG_DMAT GENMASK(7, 0)#define SDW_ALH_STRMZCFG_CHN GENMASK(19, 16) enum intel_pdi_type { INTEL_PDI_IN = 0, INTEL_PDI_OUT = 1, INTEL_PDI_BD = 2,}; #define cdns_to_intel(_cdns) container_of(_cdns, struct sdw_intel, cdns) /* * Read, write helpers for HW registers */static inline int intel_readl(void __iomem *base, int offset){ return readl(base + offset);} static inline void intel_writel(void __iomem *base, int offset, int value){ writel(value, base + offset);} static inline u16 intel_readw(void __iomem *base, int offset){ return readw(base + offset);} static inline void intel_writew(void __iomem *base, int offset, u16 value){ writew(value, base + offset);} static int intel_wait_bit(void __iomem *base, int offset, u32 mask, u32 target){ int timeout = 10; u32 reg_read; do { reg_read = readl(base + offset); if ((reg_read & mask) == target) return 0; timeout--; usleep_range(50, 100); } while (timeout != 0); return -EAGAIN;} static int intel_clear_bit(void __iomem *base, int offset, u32 value, u32 mask){ writel(value, base + offset); return intel_wait_bit(base, offset, mask, 0);} static int intel_set_bit(void __iomem *base, int offset, u32 value, u32 mask){ writel(value, base + offset); return intel_wait_bit(base, offset, mask, mask);} /* * debugfs */#ifdef CONFIG_DEBUG_FS #define RD_BUF (2 * PAGE_SIZE) static ssize_t intel_sprintf(void __iomem *mem, bool l, char *buf, size_t pos, unsigned int reg){ int value; if (l) value = intel_readl(mem, reg); else value = intel_readw(mem, reg); return scnprintf(buf + pos, RD_BUF - pos, "%4x\t%4x\n", reg, value);} static int intel_reg_show(struct seq_file *s_file, void *data){ struct sdw_intel *sdw = s_file->private; void __iomem *s = sdw->link_res->shim; void __iomem *a = sdw->link_res->alh; char *buf; ssize_t ret; int i, j; unsigned int links, reg; buf = kzalloc(RD_BUF, GFP_KERNEL); if (!buf) return -ENOMEM; links = intel_readl(s, SDW_SHIM_LCAP) & GENMASK(2, 0); ret = scnprintf(buf, RD_BUF, "Register Value\n"); ret += scnprintf(buf + ret, RD_BUF - ret, "\nShim\n"); for (i = 0; i < links; i++) { reg = SDW_SHIM_LCAP + i * 4; ret += intel_sprintf(s, true, buf, ret, reg); } for (i = 0; i < links; i++) { ret += scnprintf(buf + ret, RD_BUF - ret, "\nLink%d\n", i); ret += intel_sprintf(s, false, buf, ret, SDW_SHIM_CTLSCAP(i)); ret += intel_sprintf(s, false, buf, ret, SDW_SHIM_CTLS0CM(i)); ret += intel_sprintf(s, false, buf, ret, SDW_SHIM_CTLS1CM(i)); ret += intel_sprintf(s, false, buf, ret, SDW_SHIM_CTLS2CM(i)); ret += intel_sprintf(s, false, buf, ret, SDW_SHIM_CTLS3CM(i)); ret += intel_sprintf(s, false, buf, ret, SDW_SHIM_PCMSCAP(i)); ret += scnprintf(buf + ret, RD_BUF - ret, "\n PCMSyCH registers\n"); /* * the value 10 is the number of PDIs. We will need a * cleanup to remove hard-coded Intel configurations * from cadence_master.c */ for (j = 0; j < 10; j++) { ret += intel_sprintf(s, false, buf, ret, SDW_SHIM_PCMSYCHM(i, j)); ret += intel_sprintf(s, false, buf, ret, SDW_SHIM_PCMSYCHC(i, j)); } ret += scnprintf(buf + ret, RD_BUF - ret, "\n PDMSCAP, IOCTL, CTMCTL\n"); ret += intel_sprintf(s, false, buf, ret, SDW_SHIM_PDMSCAP(i)); ret += intel_sprintf(s, false, buf, ret, SDW_SHIM_IOCTL(i)); ret += intel_sprintf(s, false, buf, ret, SDW_SHIM_CTMCTL(i)); } ret += scnprintf(buf + ret, RD_BUF - ret, "\nWake registers\n"); ret += intel_sprintf(s, false, buf, ret, SDW_SHIM_WAKEEN); ret += intel_sprintf(s, false, buf, ret, SDW_SHIM_WAKESTS); ret += scnprintf(buf + ret, RD_BUF - ret, "\nALH STRMzCFG\n"); for (i = 0; i < SDW_ALH_NUM_STREAMS; i++) ret += intel_sprintf(a, true, buf, ret, SDW_ALH_STRMZCFG(i)); seq_printf(s_file, "%s", buf); kfree(buf); return 0;}DEFINE_SHOW_ATTRIBUTE(intel_reg); static int intel_set_m_datamode(void *data, u64 value){ struct sdw_intel *sdw = data; struct sdw_bus *bus = &sdw->cdns.bus; if (value > SDW_PORT_DATA_MODE_STATIC_1) return -EINVAL; /* Userspace changed the hardware state behind the kernel's back */ add_taint(TAINT_USER, LOCKDEP_STILL_OK); bus->params.m_data_mode = value; return 0;}DEFINE_DEBUGFS_ATTRIBUTE(intel_set_m_datamode_fops, NULL, intel_set_m_datamode, "%llu\n"); static int intel_set_s_datamode(void *data, u64 value){ struct sdw_intel *sdw = data; struct sdw_bus *bus = &sdw->cdns.bus; if (value > SDW_PORT_DATA_MODE_STATIC_1) return -EINVAL; /* Userspace changed the hardware state behind the kernel's back */ add_taint(TAINT_USER, LOCKDEP_STILL_OK); bus->params.s_data_mode = value; return 0;}DEFINE_DEBUGFS_ATTRIBUTE(intel_set_s_datamode_fops, NULL, intel_set_s_datamode, "%llu\n"); static void intel_debugfs_init(struct sdw_intel *sdw){ struct dentry *root = sdw->cdns.bus.debugfs; if (!root) return; sdw->debugfs = debugfs_create_dir("intel-sdw", root); debugfs_create_file("intel-registers", 0400, sdw->debugfs, sdw, &intel_reg_fops); debugfs_create_file("intel-m-datamode", 0200, sdw->debugfs, sdw, &intel_set_m_datamode_fops); debugfs_create_file("intel-s-datamode", 0200, sdw->debugfs, sdw, &intel_set_s_datamode_fops); sdw_cdns_debugfs_init(&sdw->cdns, sdw->debugfs);} static void intel_debugfs_exit(struct sdw_intel *sdw){ debugfs_remove_recursive(sdw->debugfs);}#elsestatic void intel_debugfs_init(struct sdw_intel *sdw) {}static void intel_debugfs_exit(struct sdw_intel *sdw) {}#endif /* CONFIG_DEBUG_FS */ /* * shim ops */ static int intel_link_power_up(struct sdw_intel *sdw){ unsigned int link_id = sdw->instance; void __iomem *shim = sdw->link_res->shim; u32 *shim_mask = sdw->link_res->shim_mask; struct sdw_bus *bus = &sdw->cdns.bus; struct sdw_master_prop *prop = &bus->prop; u32 spa_mask, cpa_mask; u32 link_control; int ret = 0; u32 syncprd; u32 sync_reg; mutex_lock(sdw->link_res->shim_lock); /* * The hardware relies on an internal counter, typically 4kHz, * to generate the SoundWire SSP - which defines a 'safe' * synchronization point between commands and audio transport * and allows for multi link synchronization. The SYNCPRD value * is only dependent on the oscillator clock provided to * the IP, so adjust based on _DSD properties reported in DSDT * tables. The values reported are based on either 24MHz * (CNL/CML) or 38.4 MHz (ICL/TGL+). */ if (prop->mclk_freq % 6000000) syncprd = SDW_SHIM_SYNC_SYNCPRD_VAL_38_4; else syncprd = SDW_SHIM_SYNC_SYNCPRD_VAL_24; if (!*shim_mask) { dev_dbg(sdw->cdns.dev, "%s: powering up all links\n", __func__); /* we first need to program the SyncPRD/CPU registers */ dev_dbg(sdw->cdns.dev, "%s: first link up, programming SYNCPRD\n", __func__); /* set SyncPRD period */ sync_reg = intel_readl(shim, SDW_SHIM_SYNC); u32p_replace_bits(&sync_reg, syncprd, SDW_SHIM_SYNC_SYNCPRD); /* Set SyncCPU bit */ sync_reg |= SDW_SHIM_SYNC_SYNCCPU; intel_writel(shim, SDW_SHIM_SYNC, sync_reg); /* Link power up sequence */ link_control = intel_readl(shim, SDW_SHIM_LCTL); /* only power-up enabled links */ spa_mask = FIELD_PREP(SDW_SHIM_LCTL_SPA_MASK, sdw->link_res->link_mask); cpa_mask = FIELD_PREP(SDW_SHIM_LCTL_CPA_MASK, sdw->link_res->link_mask); link_control |= spa_mask; ret = intel_set_bit(shim, SDW_SHIM_LCTL, link_control, cpa_mask); if (ret < 0) { dev_err(sdw->cdns.dev, "Failed to power up link: %d\n", ret); goto out; } /* SyncCPU will change once link is active */ ret = intel_wait_bit(shim, SDW_SHIM_SYNC, SDW_SHIM_SYNC_SYNCCPU, 0); if (ret < 0) { dev_err(sdw->cdns.dev, "Failed to set SHIM_SYNC: %d\n", ret); goto out; } } *shim_mask |= BIT(link_id); sdw->cdns.link_up = true;out: mutex_unlock(sdw->link_res->shim_lock); return ret;} /* this needs to be called with shim_lock */static void intel_shim_glue_to_master_ip(struct sdw_intel *sdw){ void __iomem *shim = sdw->link_res->shim; unsigned int link_id = sdw->instance; u16 ioctl; /* Switch to MIP from Glue logic */ ioctl = intel_readw(shim, SDW_SHIM_IOCTL(link_id)); ioctl &= ~(SDW_SHIM_IOCTL_DOE); intel_writew(shim, SDW_SHIM_IOCTL(link_id), ioctl); usleep_range(10, 15); ioctl &= ~(SDW_SHIM_IOCTL_DO); intel_writew(shim, SDW_SHIM_IOCTL(link_id), ioctl); usleep_range(10, 15); ioctl |= (SDW_SHIM_IOCTL_MIF); intel_writew(shim, SDW_SHIM_IOCTL(link_id), ioctl); usleep_range(10, 15); ioctl &= ~(SDW_SHIM_IOCTL_BKE); ioctl &= ~(SDW_SHIM_IOCTL_COE); intel_writew(shim, SDW_SHIM_IOCTL(link_id), ioctl); usleep_range(10, 15); /* at this point Master IP has full control of the I/Os */} /* this needs to be called with shim_lock */static void intel_shim_master_ip_to_glue(struct sdw_intel *sdw){ unsigned int link_id = sdw->instance; void __iomem *shim = sdw->link_res->shim; u16 ioctl; /* Glue logic */ ioctl = intel_readw(shim, SDW_SHIM_IOCTL(link_id)); ioctl |= SDW_SHIM_IOCTL_BKE; ioctl |= SDW_SHIM_IOCTL_COE; intel_writew(shim, SDW_SHIM_IOCTL(link_id), ioctl); usleep_range(10, 15); ioctl &= ~(SDW_SHIM_IOCTL_MIF); intel_writew(shim, SDW_SHIM_IOCTL(link_id), ioctl); usleep_range(10, 15); /* at this point Integration Glue has full control of the I/Os */} static int intel_shim_init(struct sdw_intel *sdw, bool clock_stop){ void __iomem *shim = sdw->link_res->shim; unsigned int link_id = sdw->instance; int ret = 0; u16 ioctl = 0, act = 0; mutex_lock(sdw->link_res->shim_lock); /* Initialize Shim */ ioctl |= SDW_SHIM_IOCTL_BKE; intel_writew(shim, SDW_SHIM_IOCTL(link_id), ioctl); usleep_range(10, 15); ioctl |= SDW_SHIM_IOCTL_WPDD; intel_writew(shim, SDW_SHIM_IOCTL(link_id), ioctl); usleep_range(10, 15); ioctl |= SDW_SHIM_IOCTL_DO; intel_writew(shim, SDW_SHIM_IOCTL(link_id), ioctl); usleep_range(10, 15); ioctl |= SDW_SHIM_IOCTL_DOE; intel_writew(shim, SDW_SHIM_IOCTL(link_id), ioctl); usleep_range(10, 15); intel_shim_glue_to_master_ip(sdw); u16p_replace_bits(&act, 0x1, SDW_SHIM_CTMCTL_DOAIS); act |= SDW_SHIM_CTMCTL_DACTQE; act |= SDW_SHIM_CTMCTL_DODS; intel_writew(shim, SDW_SHIM_CTMCTL(link_id), act); usleep_range(10, 15); mutex_unlock(sdw->link_res->shim_lock); Lines 1–500 of 1992. Load more
// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause) // Copyright(c) 2015-17 Intel Corporation. /* * Soundwire Intel Master Driver */ #include <linux/acpi.h> #include <linux/debugfs.h> #include <linux/delay.h> #include <linux/module.h> #include <linux/interrupt.h> #include <linux/io.h> #include <linux/platform_device.h> #include <sound/pcm_params.h> #include <linux/pm_runtime.h> #include <sound/soc.h> #include <linux/soundwire/sdw_registers.h> #include <linux/soundwire/sdw.h> #include <linux/soundwire/sdw_intel.h> #include "cadence_master.h" #include "bus.h" #include "intel.h" #define INTEL_MASTER_SUSPEND_DELAY_MS 3000 /* * debug/config flags for the Intel SoundWire Master. * * Since we may have multiple masters active, we can have up to 8 * flags reused in each byte, with master0 using the ls-byte, etc. */ #define SDW_INTEL_MASTER_DISABLE_PM_RUNTIME BIT(0) #define SDW_INTEL_MASTER_DISABLE_CLOCK_STOP BIT(1) #define SDW_INTEL_MASTER_DISABLE_PM_RUNTIME_IDLE BIT(2) #define SDW_INTEL_MASTER_DISABLE_MULTI_LINK BIT(3) static int md_flags; module_param_named(sdw_md_flags, md_flags, int, 0444); MODULE_PARM_DESC(sdw_md_flags, "SoundWire Intel Master device flags (0x0 all off)"); /* Intel SHIM Registers Definition */ #define SDW_SHIM_LCAP 0x0 #define SDW_SHIM_LCTL 0x4 #define SDW_SHIM_IPPTR 0x8 #define SDW_SHIM_SYNC 0xC #define SDW_SHIM_CTLSCAP(x) (0x010 + 0x60 * (x)) #define SDW_SHIM_CTLS0CM(x) (0x012 + 0x60 * (x)) #define SDW_SHIM_CTLS1CM(x) (0x014 + 0x60 * (x)) #define SDW_SHIM_CTLS2CM(x) (0x016 + 0x60 * (x)) #define SDW_SHIM_CTLS3CM(x) (0x018 + 0x60 * (x)) #define SDW_SHIM_PCMSCAP(x) (0x020 + 0x60 * (x)) #define SDW_SHIM_PCMSYCHM(x, y) (0x022 + (0x60 * (x)) + (0x2 * (y))) #define SDW_SHIM_PCMSYCHC(x, y) (0x042 + (0x60 * (x)) + (0x2 * (y))) #define SDW_SHIM_PDMSCAP(x) (0x062 + 0x60 * (x)) #define SDW_SHIM_IOCTL(x) (0x06C + 0x60 * (x)) #define SDW_SHIM_CTMCTL(x) (0x06E + 0x60 * (x)) #define SDW_SHIM_WAKEEN 0x190 #define SDW_SHIM_WAKESTS 0x192 #define SDW_SHIM_LCTL_SPA BIT(0) #define SDW_SHIM_LCTL_SPA_MASK GENMASK(3, 0) #define SDW_SHIM_LCTL_CPA BIT(8) #define SDW_SHIM_LCTL_CPA_MASK GENMASK(11, 8) #define SDW_SHIM_SYNC_SYNCPRD_VAL_24 (24000 / SDW_CADENCE_GSYNC_KHZ - 1) #define SDW_SHIM_SYNC_SYNCPRD_VAL_38_4 (38400 / SDW_CADENCE_GSYNC_KHZ - 1) #define SDW_SHIM_SYNC_SYNCPRD GENMASK(14, 0) #define SDW_SHIM_SYNC_SYNCCPU BIT(15) #define SDW_SHIM_SYNC_CMDSYNC_MASK GENMASK(19, 16) #define SDW_SHIM_SYNC_CMDSYNC BIT(16) #define SDW_SHIM_SYNC_SYNCGO BIT(24) #define SDW_SHIM_PCMSCAP_ISS GENMASK(3, 0) #define SDW_SHIM_PCMSCAP_OSS GENMASK(7, 4) #define SDW_SHIM_PCMSCAP_BSS GENMASK(12, 8) #define SDW_SHIM_PCMSYCM_LCHN GENMASK(3, 0) #define SDW_SHIM_PCMSYCM_HCHN GENMASK(7, 4) #define SDW_SHIM_PCMSYCM_STREAM GENMASK(13, 8) #define SDW_SHIM_PCMSYCM_DIR BIT(15) #define SDW_SHIM_PDMSCAP_ISS GENMASK(3, 0) #define SDW_SHIM_PDMSCAP_OSS GENMASK(7, 4) #define SDW_SHIM_PDMSCAP_BSS GENMASK(12, 8) #define SDW_SHIM_PDMSCAP_CPSS GENMASK(15, 13) #define SDW_SHIM_IOCTL_MIF BIT(0) #define SDW_SHIM_IOCTL_CO BIT(1) #define SDW_SHIM_IOCTL_COE BIT(2) #define SDW_SHIM_IOCTL_DO BIT(3) #define SDW_SHIM_IOCTL_DOE BIT(4) #define SDW_SHIM_IOCTL_BKE BIT(5) #define SDW_SHIM_IOCTL_WPDD BIT(6) #define SDW_SHIM_IOCTL_CIBD BIT(8) #define SDW_SHIM_IOCTL_DIBD BIT(9) #define SDW_SHIM_CTMCTL_DACTQE BIT(0) #define SDW_SHIM_CTMCTL_DODS BIT(1) #define SDW_SHIM_CTMCTL_DOAIS GENMASK(4, 3) #define SDW_SHIM_WAKEEN_ENABLE BIT(0) #define SDW_SHIM_WAKESTS_STATUS BIT(0) /* Intel ALH Register definitions */ #define SDW_ALH_STRMZCFG(x) (0x000 + (0x4 * (x))) #define SDW_ALH_NUM_STREAMS 64 #define SDW_ALH_STRMZCFG_DMAT_VAL 0x3 #define SDW_ALH_STRMZCFG_DMAT GENMASK(7, 0) #define SDW_ALH_STRMZCFG_CHN GENMASK(19, 16) enum intel_pdi_type { INTEL_PDI_IN = 0, INTEL_PDI_OUT = 1, INTEL_PDI_BD = 2, }; #define cdns_to_intel(_cdns) container_of(_cdns, struct sdw_intel, cdns) /* * Read, write helpers for HW registers */ static inline int intel_readl(void __iomem *base, int offset) { return readl(base + offset); } static inline void intel_writel(void __iomem *base, int offset, int value) { writel(value, base + offset); } static inline u16 intel_readw(void __iomem *base, int offset) { return readw(base + offset); } static inline void intel_writew(void __iomem *base, int offset, u16 value) { writew(value, base + offset); } static int intel_wait_bit(void __iomem *base, int offset, u32 mask, u32 target) { int timeout = 10; u32 reg_read; do { reg_read = readl(base + offset); if ((reg_read & mask) == target) return 0; timeout--; usleep_range(50, 100); } while (timeout != 0); return -EAGAIN; } static int intel_clear_bit(void __iomem *base, int offset, u32 value, u32 mask) { writel(value, base + offset); return intel_wait_bit(base, offset, mask, 0); } static int intel_set_bit(void __iomem *base, int offset, u32 value, u32 mask) { writel(value, base + offset); return intel_wait_bit(base, offset, mask, mask); } /* * debugfs */ #ifdef CONFIG_DEBUG_FS #define RD_BUF (2 * PAGE_SIZE) static ssize_t intel_sprintf(void __iomem *mem, bool l, char *buf, size_t pos, unsigned int reg) { int value; if (l) value = intel_readl(mem, reg); else value = intel_readw(mem, reg); return scnprintf(buf + pos, RD_BUF - pos, "%4x\t%4x\n", reg, value); } static int intel_reg_show(struct seq_file *s_file, void *data) { struct sdw_intel *sdw = s_file->private; void __iomem *s = sdw->link_res->shim; void __iomem *a = sdw->link_res->alh; char *buf; ssize_t ret; int i, j; unsigned int links, reg; buf = kzalloc(RD_BUF, GFP_KERNEL); if (!buf) return -ENOMEM; links = intel_readl(s, SDW_SHIM_LCAP) & GENMASK(2, 0); ret = scnprintf(buf, RD_BUF, "Register Value\n"); ret += scnprintf(buf + ret, RD_BUF - ret, "\nShim\n"); for (i = 0; i < links; i++) { reg = SDW_SHIM_LCAP + i * 4; ret += intel_sprintf(s, true, buf, ret, reg); } for (i = 0; i < links; i++) { ret += scnprintf(buf + ret, RD_BUF - ret, "\nLink%d\n", i); ret += intel_sprintf(s, false, buf, ret, SDW_SHIM_CTLSCAP(i)); ret += intel_sprintf(s, false, buf, ret, SDW_SHIM_CTLS0CM(i)); ret += intel_sprintf(s, false, buf, ret, SDW_SHIM_CTLS1CM(i)); ret += intel_sprintf(s, false, buf, ret, SDW_SHIM_CTLS2CM(i)); ret += intel_sprintf(s, false, buf, ret, SDW_SHIM_CTLS3CM(i)); ret += intel_sprintf(s, false, buf, ret, SDW_SHIM_PCMSCAP(i)); ret += scnprintf(buf + ret, RD_BUF - ret, "\n PCMSyCH registers\n"); /* * the value 10 is the number of PDIs. We will need a * cleanup to remove hard-coded Intel configurations * from cadence_master.c */ for (j = 0; j < 10; j++) { ret += intel_sprintf(s, false, buf, ret, SDW_SHIM_PCMSYCHM(i, j)); ret += intel_sprintf(s, false, buf, ret, SDW_SHIM_PCMSYCHC(i, j)); } ret += scnprintf(buf + ret, RD_BUF - ret, "\n PDMSCAP, IOCTL, CTMCTL\n"); ret += intel_sprintf(s, false, buf, ret, SDW_SHIM_PDMSCAP(i)); ret += intel_sprintf(s, false, buf, ret, SDW_SHIM_IOCTL(i)); ret += intel_sprintf(s, false, buf, ret, SDW_SHIM_CTMCTL(i)); } ret += scnprintf(buf + ret, RD_BUF - ret, "\nWake registers\n"); ret += intel_sprintf(s, false, buf, ret, SDW_SHIM_WAKEEN); ret += intel_sprintf(s, false, buf, ret, SDW_SHIM_WAKESTS); ret += scnprintf(buf + ret, RD_BUF - ret, "\nALH STRMzCFG\n"); for (i = 0; i < SDW_ALH_NUM_STREAMS; i++) ret += intel_sprintf(a, true, buf, ret, SDW_ALH_STRMZCFG(i)); seq_printf(s_file, "%s", buf); kfree(buf); return 0; } DEFINE_SHOW_ATTRIBUTE(intel_reg); static int intel_set_m_datamode(void *data, u64 value) { struct sdw_intel *sdw = data; struct sdw_bus *bus = &sdw->cdns.bus; if (value > SDW_PORT_DATA_MODE_STATIC_1) return -EINVAL; /* Userspace changed the hardware state behind the kernel's back */ add_taint(TAINT_USER, LOCKDEP_STILL_OK); bus->params.m_data_mode = value; return 0; } DEFINE_DEBUGFS_ATTRIBUTE(intel_set_m_datamode_fops, NULL, intel_set_m_datamode, "%llu\n"); static int intel_set_s_datamode(void *data, u64 value) { struct sdw_intel *sdw = data; struct sdw_bus *bus = &sdw->cdns.bus; if (value > SDW_PORT_DATA_MODE_STATIC_1) return -EINVAL; /* Userspace changed the hardware state behind the kernel's back */ add_taint(TAINT_USER, LOCKDEP_STILL_OK); bus->params.s_data_mode = value; return 0; } DEFINE_DEBUGFS_ATTRIBUTE(intel_set_s_datamode_fops, NULL, intel_set_s_datamode, "%llu\n"); static void intel_debugfs_init(struct sdw_intel *sdw) { struct dentry *root = sdw->cdns.bus.debugfs; if (!root) return; sdw->debugfs = debugfs_create_dir("intel-sdw", root); debugfs_create_file("intel-registers", 0400, sdw->debugfs, sdw, &intel_reg_fops); debugfs_create_file("intel-m-datamode", 0200, sdw->debugfs, sdw, &intel_set_m_datamode_fops); debugfs_create_file("intel-s-datamode", 0200, sdw->debugfs, sdw, &intel_set_s_datamode_fops); sdw_cdns_debugfs_init(&sdw->cdns, sdw->debugfs); } static void intel_debugfs_exit(struct sdw_intel *sdw) { debugfs_remove_recursive(sdw->debugfs); } #else static void intel_debugfs_init(struct sdw_intel *sdw) {} static void intel_debugfs_exit(struct sdw_intel *sdw) {} #endif /* CONFIG_DEBUG_FS */ /* * shim ops */ static int intel_link_power_up(struct sdw_intel *sdw) { unsigned int link_id = sdw->instance; void __iomem *shim = sdw->link_res->shim; u32 *shim_mask = sdw->link_res->shim_mask; struct sdw_bus *bus = &sdw->cdns.bus; struct sdw_master_prop *prop = &bus->prop; u32 spa_mask, cpa_mask; u32 link_control; int ret = 0; u32 syncprd; u32 sync_reg; mutex_lock(sdw->link_res->shim_lock); /* * The hardware relies on an internal counter, typically 4kHz, * to generate the SoundWire SSP - which defines a 'safe' * synchronization point between commands and audio transport * and allows for multi link synchronization. The SYNCPRD value * is only dependent on the oscillator clock provided to * the IP, so adjust based on _DSD properties reported in DSDT * tables. The values reported are based on either 24MHz * (CNL/CML) or 38.4 MHz (ICL/TGL+). */ if (prop->mclk_freq % 6000000) syncprd = SDW_SHIM_SYNC_SYNCPRD_VAL_38_4; else syncprd = SDW_SHIM_SYNC_SYNCPRD_VAL_24; if (!*shim_mask) { dev_dbg(sdw->cdns.dev, "%s: powering up all links\n", __func__); /* we first need to program the SyncPRD/CPU registers */ dev_dbg(sdw->cdns.dev, "%s: first link up, programming SYNCPRD\n", __func__); /* set SyncPRD period */ sync_reg = intel_readl(shim, SDW_SHIM_SYNC); u32p_replace_bits(&sync_reg, syncprd, SDW_SHIM_SYNC_SYNCPRD); /* Set SyncCPU bit */ sync_reg |= SDW_SHIM_SYNC_SYNCCPU; intel_writel(shim, SDW_SHIM_SYNC, sync_reg); /* Link power up sequence */ link_control = intel_readl(shim, SDW_SHIM_LCTL); /* only power-up enabled links */ spa_mask = FIELD_PREP(SDW_SHIM_LCTL_SPA_MASK, sdw->link_res->link_mask); cpa_mask = FIELD_PREP(SDW_SHIM_LCTL_CPA_MASK, sdw->link_res->link_mask); link_control |= spa_mask; ret = intel_set_bit(shim, SDW_SHIM_LCTL, link_control, cpa_mask); if (ret < 0) { dev_err(sdw->cdns.dev, "Failed to power up link: %d\n", ret); goto out; } /* SyncCPU will change once link is active */ ret = intel_wait_bit(shim, SDW_SHIM_SYNC, SDW_SHIM_SYNC_SYNCCPU, 0); if (ret < 0) { dev_err(sdw->cdns.dev, "Failed to set SHIM_SYNC: %d\n", ret); goto out; } } *shim_mask |= BIT(link_id); sdw->cdns.link_up = true; out: mutex_unlock(sdw->link_res->shim_lock); return ret; } /* this needs to be called with shim_lock */ static void intel_shim_glue_to_master_ip(struct sdw_intel *sdw) { void __iomem *shim = sdw->link_res->shim; unsigned int link_id = sdw->instance; u16 ioctl; /* Switch to MIP from Glue logic */ ioctl = intel_readw(shim, SDW_SHIM_IOCTL(link_id)); ioctl &= ~(SDW_SHIM_IOCTL_DOE); intel_writew(shim, SDW_SHIM_IOCTL(link_id), ioctl); usleep_range(10, 15); ioctl &= ~(SDW_SHIM_IOCTL_DO); intel_writew(shim, SDW_SHIM_IOCTL(link_id), ioctl); usleep_range(10, 15); ioctl |= (SDW_SHIM_IOCTL_MIF); intel_writew(shim, SDW_SHIM_IOCTL(link_id), ioctl); usleep_range(10, 15); ioctl &= ~(SDW_SHIM_IOCTL_BKE); ioctl &= ~(SDW_SHIM_IOCTL_COE); intel_writew(shim, SDW_SHIM_IOCTL(link_id), ioctl); usleep_range(10, 15); /* at this point Master IP has full control of the I/Os */ } /* this needs to be called with shim_lock */ static void intel_shim_master_ip_to_glue(struct sdw_intel *sdw) { unsigned int link_id = sdw->instance; void __iomem *shim = sdw->link_res->shim; u16 ioctl; /* Glue logic */ ioctl = intel_readw(shim, SDW_SHIM_IOCTL(link_id)); ioctl |= SDW_SHIM_IOCTL_BKE; ioctl |= SDW_SHIM_IOCTL_COE; intel_writew(shim, SDW_SHIM_IOCTL(link_id), ioctl); usleep_range(10, 15); ioctl &= ~(SDW_SHIM_IOCTL_MIF); intel_writew(shim, SDW_SHIM_IOCTL(link_id), ioctl); usleep_range(10, 15); /* at this point Integration Glue has full control of the I/Os */ } static int intel_shim_init(struct sdw_intel *sdw, bool clock_stop) { void __iomem *shim = sdw->link_res->shim; unsigned int link_id = sdw->instance; int ret = 0; u16 ioctl = 0, act = 0; mutex_lock(sdw->link_res->shim_lock); /* Initialize Shim */ ioctl |= SDW_SHIM_IOCTL_BKE; intel_writew(shim, SDW_SHIM_IOCTL(link_id), ioctl); usleep_range(10, 15); ioctl |= SDW_SHIM_IOCTL_WPDD; intel_writew(shim, SDW_SHIM_IOCTL(link_id), ioctl); usleep_range(10, 15); ioctl |= SDW_SHIM_IOCTL_DO; intel_writew(shim, SDW_SHIM_IOCTL(link_id), ioctl); usleep_range(10, 15); ioctl |= SDW_SHIM_IOCTL_DOE; intel_writew(shim, SDW_SHIM_IOCTL(link_id), ioctl); usleep_range(10, 15); intel_shim_glue_to_master_ip(sdw); u16p_replace_bits(&act, 0x1, SDW_SHIM_CTMCTL_DOAIS); act |= SDW_SHIM_CTMCTL_DACTQE; act |= SDW_SHIM_CTMCTL_DODS; intel_writew(shim, SDW_SHIM_CTMCTL(link_id), act); usleep_range(10, 15); mutex_unlock(sdw->link_res->shim_lock);