// 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/cleanup.h>#include <linux/debugfs.h>#include <linux/delay.h>#include <linux/io.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" 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; ssize_t ret; int i, j; unsigned int links, reg; char *buf __free(kfree) = kzalloc(RD_BUF, GFP_KERNEL); if (!buf) return -ENOMEM; links = intel_readl(s, SDW_SHIM_LCAP) & SDW_SHIM_LCAP_LCOUNT_MASK; 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 IOCTL, CTMCTL\n"); 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); 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 *//* 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 */} /* this needs to be called with shim_lock */static void intel_shim_init(struct sdw_intel *sdw){ void __iomem *shim = sdw->link_res->shim; unsigned int link_id = sdw->instance; u16 ioctl = 0, act; /* 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); act = intel_readw(shim, SDW_SHIM_CTMCTL(link_id)); 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);} static int intel_shim_check_wake(struct sdw_intel *sdw){ void __iomem *shim; u16 wake_sts; shim = sdw->link_res->shim; wake_sts = intel_readw(shim, SDW_SHIM_WAKESTS); return wake_sts & BIT(sdw->instance);} static void intel_shim_wake(struct sdw_intel *sdw, bool wake_enable){ void __iomem *shim = sdw->link_res->shim; unsigned int link_id = sdw->instance; u16 wake_en, wake_sts; mutex_lock(sdw->link_res->shim_lock); wake_en = intel_readw(shim, SDW_SHIM_WAKEEN); if (wake_enable) { /* Enable the wakeup */ wake_en |= (SDW_SHIM_WAKEEN_ENABLE << link_id); intel_writew(shim, SDW_SHIM_WAKEEN, wake_en); } else { /* Disable the wake up interrupt */ wake_en &= ~(SDW_SHIM_WAKEEN_ENABLE << link_id); intel_writew(shim, SDW_SHIM_WAKEEN, wake_en); /* Clear wake status */ wake_sts = intel_readw(shim, SDW_SHIM_WAKESTS); wake_sts |= (SDW_SHIM_WAKESTS_STATUS << link_id); intel_writew(shim, SDW_SHIM_WAKESTS, wake_sts); } mutex_unlock(sdw->link_res->shim_lock);} static bool intel_check_cmdsync_unlocked(struct sdw_intel *sdw){ void __iomem *shim = sdw->link_res->shim; int sync_reg; sync_reg = intel_readl(shim, SDW_SHIM_SYNC); return !!(sync_reg & SDW_SHIM_SYNC_CMDSYNC_MASK);} 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 clock_source; u32 syncprd; u32 sync_reg; bool lcap_mlcs; 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+). On MeteorLake additional * frequencies are available with the MLCS clock source selection. */ lcap_mlcs = intel_readl(shim, SDW_SHIM_LCAP) & SDW_SHIM_LCAP_MLCS_MASK; if (prop->mclk_freq % 6000000) { if (prop->mclk_freq % 2400000) { if (lcap_mlcs) { syncprd = SDW_SHIM_SYNC_SYNCPRD_VAL_24_576; clock_source = SDW_SHIM_MLCS_CARDINAL_CLK; } else { dev_err(sdw->cdns.dev, "%s: invalid clock configuration, mclk %d lcap_mlcs %d\n", __func__, prop->mclk_freq, lcap_mlcs); ret = -EINVAL; goto out; } } else { syncprd = SDW_SHIM_SYNC_SYNCPRD_VAL_38_4; clock_source = SDW_SHIM_MLCS_XTAL_CLK; } } else { if (lcap_mlcs) { syncprd = SDW_SHIM_SYNC_SYNCPRD_VAL_96; clock_source = SDW_SHIM_MLCS_AUDIO_PLL_CLK; } else { syncprd = SDW_SHIM_SYNC_SYNCPRD_VAL_24; clock_source = SDW_SHIM_MLCS_XTAL_CLK; } } if (!*shim_mask) { dev_dbg(sdw->cdns.dev, "powering up all links\n"); /* we first need to program the SyncPRD/CPU registers */ dev_dbg(sdw->cdns.dev, "first link up, programming SYNCPRD\n"); /* 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; } /* update link clock if needed */ if (lcap_mlcs) { link_control = intel_readl(shim, SDW_SHIM_LCTL); u32p_replace_bits(&link_control, clock_source, SDW_SHIM_LCTL_MLCS_MASK); intel_writel(shim, SDW_SHIM_LCTL, link_control); } } *shim_mask |= BIT(link_id); sdw->cdns.link_up = true; intel_shim_init(sdw); out: mutex_unlock(sdw->link_res->shim_lock); return ret;} static int intel_link_power_down(struct sdw_intel *sdw){ u32 link_control, spa_mask, cpa_mask; unsigned int link_id = sdw->instance; void __iomem *shim = sdw->link_res->shim; u32 *shim_mask = sdw->link_res->shim_mask; int ret = 0; mutex_lock(sdw->link_res->shim_lock); if (!(*shim_mask & BIT(link_id))) dev_err(sdw->cdns.dev, "%s: Unbalanced power-up/down calls\n", __func__); sdw->cdns.link_up = false; intel_shim_master_ip_to_glue(sdw); *shim_mask &= ~BIT(link_id); if (!*shim_mask) { dev_dbg(sdw->cdns.dev, "powering down all links\n"); /* Link power down sequence */ link_control = intel_readl(shim, SDW_SHIM_LCTL); /* only power-down 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_clear_bit(shim, SDW_SHIM_LCTL, link_control, cpa_mask); if (ret < 0) { dev_err(sdw->cdns.dev, "%s: could not power down link\n", __func__); /* * we leave the sdw->cdns.link_up flag as false since we've disabled * the link at this point and cannot handle interrupts any longer. */ } } mutex_unlock(sdw->link_res->shim_lock); return ret;} static void intel_shim_sync_arm(struct sdw_intel *sdw)Lines 1–500 of 1096. 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/cleanup.h> #include <linux/debugfs.h> #include <linux/delay.h> #include <linux/io.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" 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; ssize_t ret; int i, j; unsigned int links, reg; char *buf __free(kfree) = kzalloc(RD_BUF, GFP_KERNEL); if (!buf) return -ENOMEM; links = intel_readl(s, SDW_SHIM_LCAP) & SDW_SHIM_LCAP_LCOUNT_MASK; 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 IOCTL, CTMCTL\n"); 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); 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 */ /* 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 */ } /* this needs to be called with shim_lock */ static void intel_shim_init(struct sdw_intel *sdw) { void __iomem *shim = sdw->link_res->shim; unsigned int link_id = sdw->instance; u16 ioctl = 0, act; /* 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); act = intel_readw(shim, SDW_SHIM_CTMCTL(link_id)); 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); } static int intel_shim_check_wake(struct sdw_intel *sdw) { void __iomem *shim; u16 wake_sts; shim = sdw->link_res->shim; wake_sts = intel_readw(shim, SDW_SHIM_WAKESTS); return wake_sts & BIT(sdw->instance); } static void intel_shim_wake(struct sdw_intel *sdw, bool wake_enable) { void __iomem *shim = sdw->link_res->shim; unsigned int link_id = sdw->instance; u16 wake_en, wake_sts; mutex_lock(sdw->link_res->shim_lock); wake_en = intel_readw(shim, SDW_SHIM_WAKEEN); if (wake_enable) { /* Enable the wakeup */ wake_en |= (SDW_SHIM_WAKEEN_ENABLE << link_id); intel_writew(shim, SDW_SHIM_WAKEEN, wake_en); } else { /* Disable the wake up interrupt */ wake_en &= ~(SDW_SHIM_WAKEEN_ENABLE << link_id); intel_writew(shim, SDW_SHIM_WAKEEN, wake_en); /* Clear wake status */ wake_sts = intel_readw(shim, SDW_SHIM_WAKESTS); wake_sts |= (SDW_SHIM_WAKESTS_STATUS << link_id); intel_writew(shim, SDW_SHIM_WAKESTS, wake_sts); } mutex_unlock(sdw->link_res->shim_lock); } static bool intel_check_cmdsync_unlocked(struct sdw_intel *sdw) { void __iomem *shim = sdw->link_res->shim; int sync_reg; sync_reg = intel_readl(shim, SDW_SHIM_SYNC); return !!(sync_reg & SDW_SHIM_SYNC_CMDSYNC_MASK); } 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 clock_source; u32 syncprd; u32 sync_reg; bool lcap_mlcs; 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+). On MeteorLake additional * frequencies are available with the MLCS clock source selection. */ lcap_mlcs = intel_readl(shim, SDW_SHIM_LCAP) & SDW_SHIM_LCAP_MLCS_MASK; if (prop->mclk_freq % 6000000) { if (prop->mclk_freq % 2400000) { if (lcap_mlcs) { syncprd = SDW_SHIM_SYNC_SYNCPRD_VAL_24_576; clock_source = SDW_SHIM_MLCS_CARDINAL_CLK; } else { dev_err(sdw->cdns.dev, "%s: invalid clock configuration, mclk %d lcap_mlcs %d\n", __func__, prop->mclk_freq, lcap_mlcs); ret = -EINVAL; goto out; } } else { syncprd = SDW_SHIM_SYNC_SYNCPRD_VAL_38_4; clock_source = SDW_SHIM_MLCS_XTAL_CLK; } } else { if (lcap_mlcs) { syncprd = SDW_SHIM_SYNC_SYNCPRD_VAL_96; clock_source = SDW_SHIM_MLCS_AUDIO_PLL_CLK; } else { syncprd = SDW_SHIM_SYNC_SYNCPRD_VAL_24; clock_source = SDW_SHIM_MLCS_XTAL_CLK; } } if (!*shim_mask) { dev_dbg(sdw->cdns.dev, "powering up all links\n"); /* we first need to program the SyncPRD/CPU registers */ dev_dbg(sdw->cdns.dev, "first link up, programming SYNCPRD\n"); /* 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; } /* update link clock if needed */ if (lcap_mlcs) { link_control = intel_readl(shim, SDW_SHIM_LCTL); u32p_replace_bits(&link_control, clock_source, SDW_SHIM_LCTL_MLCS_MASK); intel_writel(shim, SDW_SHIM_LCTL, link_control); } } *shim_mask |= BIT(link_id); sdw->cdns.link_up = true; intel_shim_init(sdw); out: mutex_unlock(sdw->link_res->shim_lock); return ret; } static int intel_link_power_down(struct sdw_intel *sdw) { u32 link_control, spa_mask, cpa_mask; unsigned int link_id = sdw->instance; void __iomem *shim = sdw->link_res->shim; u32 *shim_mask = sdw->link_res->shim_mask; int ret = 0; mutex_lock(sdw->link_res->shim_lock); if (!(*shim_mask & BIT(link_id))) dev_err(sdw->cdns.dev, "%s: Unbalanced power-up/down calls\n", __func__); sdw->cdns.link_up = false; intel_shim_master_ip_to_glue(sdw); *shim_mask &= ~BIT(link_id); if (!*shim_mask) { dev_dbg(sdw->cdns.dev, "powering down all links\n"); /* Link power down sequence */ link_control = intel_readl(shim, SDW_SHIM_LCTL); /* only power-down 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_clear_bit(shim, SDW_SHIM_LCTL, link_control, cpa_mask); if (ret < 0) { dev_err(sdw->cdns.dev, "%s: could not power down link\n", __func__); /* * we leave the sdw->cdns.link_up flag as false since we've disabled * the link at this point and cannot handle interrupts any longer. */ } } mutex_unlock(sdw->link_res->shim_lock); return ret; } static void intel_shim_sync_arm(struct sdw_intel *sdw)