diff --git a/Documentation/driver-api/soundwire/stream.rst b/Documentation/driver-api/soundwire/stream.rst index 5351bd2f34a870..9b7418ff8d59aa 100644 --- a/Documentation/driver-api/soundwire/stream.rst +++ b/Documentation/driver-api/soundwire/stream.rst @@ -156,22 +156,27 @@ Below shows the SoundWire stream states and state transition diagram. :: +-----------+ +------------+ +----------+ +----------+ | ALLOCATED +---->| CONFIGURED +---->| PREPARED +---->| ENABLED | | STATE | | STATE | | STATE | | STATE | - +-----------+ +------------+ +----------+ +----+-----+ - ^ - | - | - v - +----------+ +------------+ +----+-----+ + +-----------+ +------------+ +---+--+---+ +----+-----+ + ^ ^ ^ + | | | + __| |___________ | + | | | + v | v + +----------+ +-----+------+ +-+--+-----+ | RELEASED |<----------+ DEPREPARED |<-------+ DISABLED | | STATE | | STATE | | STATE | +----------+ +------------+ +----------+ -NOTE: State transition between prepare and deprepare is supported in Spec -but not in the software (subsystem) +NOTE: State transitions between ``SDW_STREAM_ENABLED`` and +``SDW_STREAM_DISABLED`` are only relevant when then INFO_PAUSE flag is +supported at the ALSA/ASoC level. Likewise the transition between +``SDW_DISABLED_STATE`` and ``SDW_PREPARED_STATE`` depends on the +INFO_RESUME flag. -NOTE2: Stream state transition checks need to be handled by caller -framework, for example ALSA/ASoC. No checks for stream transition exist in -SoundWire subsystem. +NOTE2: The framework implements basic state transition checks, but +does not e.g. check if a transition from DISABLED to ENABLED is valid +on a specific platform. Such tests need to be added at the ALSA/ASoC +level. Stream State Operations ----------------------- @@ -246,6 +251,9 @@ SDW_STREAM_PREPARED Prepare state of stream. Operations performed before entering in this state: + (0) Steps 1 and 2 are omitted in the case of a resume operation, + where the bus bandwidth is known. + (1) Bus parameters such as bandwidth, frame shape, clock frequency, are computed based on current stream as well as already active stream(s) on Bus. Re-computation is required to accommodate current @@ -270,13 +278,15 @@ Prepare state of stream. Operations performed before entering in this state: After all above operations are successful, stream state is set to ``SDW_STREAM_PREPARED``. -Bus implements below API for PREPARE state which needs to be called once per -stream. From ASoC DPCM framework, this stream state is linked to -.prepare() operation. +Bus implements below API for PREPARE state which needs to be called +once per stream. From ASoC DPCM framework, this stream state is linked +to .prepare() operation. Since the .trigger() operations may not +follow the .prepare(), a direct transitions from +``SDW_STREAM_PREPARED`` to ``SDW_STREAM_DEPREPARED`` is allowed. .. code-block:: c - int sdw_prepare_stream(struct sdw_stream_runtime * stream); + int sdw_prepare_stream(struct sdw_stream_runtime * stream, bool resume); SDW_STREAM_ENABLED @@ -332,6 +342,14 @@ Bus implements below API for DISABLED state which needs to be called once per stream. From ASoC DPCM framework, this stream state is linked to .trigger() stop operation. +When the INFO_PAUSE flag is supported, a direct transition to +``SDW_STREAM_ENABLED`` is allowed. + +For resume operations where ASoC will use the .prepare() callback, the +stream can transition from ``SDW_STREAM_DISABLED`` to +``SDW_STREAM_PREPARED``, with all required settings restored but +without updating the bandwidth and bit allocation. + .. code-block:: c int sdw_disable_stream(struct sdw_stream_runtime * stream); @@ -353,9 +371,18 @@ state: After all above operations are successful, stream state is set to ``SDW_STREAM_DEPREPARED``. -Bus implements below API for DEPREPARED state which needs to be called once -per stream. From ASoC DPCM framework, this stream state is linked to -.trigger() stop operation. +Bus implements below API for DEPREPARED state which needs to be called +once per stream. ALSA/ASoC do not have a concept of 'deprepare', and +the mapping from this stream state to ALSA/ASoC operation may be +implementation specific. + +When the INFO_PAUSE flag is supported, the stream state is linked to +the .hw_free() operation - the stream is not deprepared on a +TRIGGER_STOP. + +Other implementations may transition to the ``SDW_STREAM_DEPREPARED`` +state on TRIGGER_STOP, should they require a transition through the +``SDW_STREAM_PREPARED`` state. .. code-block:: c diff --git a/drivers/base/regmap/regmap-sdw.c b/drivers/base/regmap/regmap-sdw.c index 50a66382d87d09..d1fc0c22180a17 100644 --- a/drivers/base/regmap/regmap-sdw.c +++ b/drivers/base/regmap/regmap-sdw.c @@ -10,7 +10,7 @@ static int regmap_sdw_write(void *context, unsigned int reg, unsigned int val) { struct device *dev = context; - struct sdw_slave *slave = dev_to_sdw_dev(dev); + struct sdw_slave *slave = to_sdw_slave_device(dev); return sdw_write(slave, reg, val); } @@ -18,7 +18,7 @@ static int regmap_sdw_write(void *context, unsigned int reg, unsigned int val) static int regmap_sdw_read(void *context, unsigned int reg, unsigned int *val) { struct device *dev = context; - struct sdw_slave *slave = dev_to_sdw_dev(dev); + struct sdw_slave *slave = to_sdw_slave_device(dev); int read; read = sdw_read(slave, reg); diff --git a/drivers/soundwire/Kconfig b/drivers/soundwire/Kconfig index c725d0a8b288b6..eb73c91b7dba87 100644 --- a/drivers/soundwire/Kconfig +++ b/drivers/soundwire/Kconfig @@ -24,6 +24,7 @@ config SOUNDWIRE_CADENCE config SOUNDWIRE_INTEL tristate "Intel SoundWire Master driver" select SOUNDWIRE_CADENCE + select SOUNDWIRE_GENERIC_ALLOCATION depends on ACPI && SND_SOC help SoundWire Intel Master driver. @@ -31,4 +32,7 @@ config SOUNDWIRE_INTEL enable this config option to get the SoundWire support for that device. +config SOUNDWIRE_GENERIC_ALLOCATION + tristate + endif diff --git a/drivers/soundwire/Makefile b/drivers/soundwire/Makefile index 563894e5ecaf50..76a5c52b12b412 100644 --- a/drivers/soundwire/Makefile +++ b/drivers/soundwire/Makefile @@ -2,11 +2,15 @@ # # Makefile for soundwire core # +ccflags-y += -DDEBUG #Bus Objs -soundwire-bus-objs := bus_type.o bus.o slave.o mipi_disco.o stream.o +soundwire-bus-objs := bus_type.o bus.o master.o slave.o mipi_disco.o stream.o obj-$(CONFIG_SOUNDWIRE) += soundwire-bus.o +soundwire-generic-allocation-objs := generic_bandwidth_allocation.o +obj-$(CONFIG_SOUNDWIRE_GENERIC_ALLOCATION) += soundwire-generic-allocation.o + ifdef CONFIG_DEBUG_FS soundwire-bus-objs += debugfs.o endif diff --git a/drivers/soundwire/bus.c b/drivers/soundwire/bus.c index fc53dbe57f8545..f255bad443851b 100644 --- a/drivers/soundwire/bus.c +++ b/drivers/soundwire/bus.c @@ -30,6 +30,12 @@ int sdw_add_bus_master(struct sdw_bus *bus) return -EINVAL; } + if (!bus->compute_params) { + dev_err(bus->dev, + "Bandwidth allocation not configured, compute_parems no set\n"); + return -EINVAL; + } + mutex_init(&bus->msg_lock); mutex_init(&bus->bus_lock); INIT_LIST_HEAD(&bus->slaves); @@ -110,9 +116,11 @@ EXPORT_SYMBOL(sdw_add_bus_master); static int sdw_delete_slave(struct device *dev, void *data) { - struct sdw_slave *slave = dev_to_sdw_dev(dev); + struct sdw_slave *slave = to_sdw_slave_device(dev); struct sdw_bus *bus = slave->bus; + pm_runtime_disable(dev); + sdw_slave_debugfs_exit(slave); mutex_lock(&bus->bus_lock); @@ -317,6 +325,46 @@ int sdw_fill_msg(struct sdw_msg *msg, struct sdw_slave *slave, return 0; } +/* + * Read/Write IO functions. + * no_pm versions can only be called by the bus, e.g. while enumerating or + * handling suspend-resume sequences. + * all clients need to use the pm versions + */ + +static int +sdw_nread_no_pm(struct sdw_slave *slave, u32 addr, size_t count, u8 *val) +{ + struct sdw_msg msg; + int ret; + + ret = sdw_fill_msg(&msg, slave, addr, count, + slave->dev_num, SDW_MSG_FLAG_READ, val); + if (ret < 0) + return ret; + + return sdw_transfer(slave->bus, &msg); +} + +static int +sdw_nwrite_no_pm(struct sdw_slave *slave, u32 addr, size_t count, u8 *val) +{ + struct sdw_msg msg; + int ret; + + ret = sdw_fill_msg(&msg, slave, addr, count, + slave->dev_num, SDW_MSG_FLAG_WRITE, val); + if (ret < 0) + return ret; + + return sdw_transfer(slave->bus, &msg); +} + +int sdw_write_no_pm(struct sdw_slave *slave, u32 addr, u8 value) +{ + return sdw_nwrite_no_pm(slave, addr, 1, &value); +} + /** * sdw_nread() - Read "n" contiguous SDW Slave registers * @slave: SDW Slave @@ -326,19 +374,17 @@ int sdw_fill_msg(struct sdw_msg *msg, struct sdw_slave *slave, */ int sdw_nread(struct sdw_slave *slave, u32 addr, size_t count, u8 *val) { - struct sdw_msg msg; int ret; - ret = sdw_fill_msg(&msg, slave, addr, count, - slave->dev_num, SDW_MSG_FLAG_READ, val); - if (ret < 0) - return ret; - ret = pm_runtime_get_sync(slave->bus->dev); - if (ret < 0) + if (ret < 0 && ret != -EACCES) { + pm_runtime_put_noidle(slave->bus->dev); return ret; + } - ret = sdw_transfer(slave->bus, &msg); + ret = sdw_nread_no_pm(slave, addr, count, val); + + pm_runtime_mark_last_busy(slave->bus->dev); pm_runtime_put(slave->bus->dev); return ret; @@ -354,19 +400,17 @@ EXPORT_SYMBOL(sdw_nread); */ int sdw_nwrite(struct sdw_slave *slave, u32 addr, size_t count, u8 *val) { - struct sdw_msg msg; int ret; - ret = sdw_fill_msg(&msg, slave, addr, count, - slave->dev_num, SDW_MSG_FLAG_WRITE, val); - if (ret < 0) - return ret; - ret = pm_runtime_get_sync(slave->bus->dev); - if (ret < 0) + if (ret < 0 && ret != -EACCES) { + pm_runtime_put_noidle(slave->bus->dev); return ret; + } - ret = sdw_transfer(slave->bus, &msg); + ret = sdw_nwrite_no_pm(slave, addr, count, val); + + pm_runtime_mark_last_busy(slave->bus->dev); pm_runtime_put(slave->bus->dev); return ret; @@ -422,10 +466,11 @@ static struct sdw_slave *sdw_get_slave(struct sdw_bus *bus, int i) static int sdw_compare_devid(struct sdw_slave *slave, struct sdw_slave_id id) { - if (slave->id.unique_id != id.unique_id || - slave->id.mfg_id != id.mfg_id || + if (slave->id.mfg_id != id.mfg_id || slave->id.part_id != id.part_id || - slave->id.class_id != id.class_id) + slave->id.class_id != id.class_id || + (slave->id.unique_id != SDW_IGNORED_UNIQUE_ID && + slave->id.unique_id != id.unique_id)) return -ENODEV; return 0; @@ -455,28 +500,43 @@ static int sdw_get_device_num(struct sdw_slave *slave) static int sdw_assign_device_num(struct sdw_slave *slave) { int ret, dev_num; + bool new_device = false; + + dev_dbg(slave->bus->dev, "in %s\n", __func__); + + + dev_dbg(slave->bus->dev, "in %s\n", __func__); /* check first if device number is assigned, if so reuse that */ if (!slave->dev_num) { - mutex_lock(&slave->bus->bus_lock); - dev_num = sdw_get_device_num(slave); - mutex_unlock(&slave->bus->bus_lock); - if (dev_num < 0) { - dev_err(slave->bus->dev, "Get dev_num failed: %d\n", - dev_num); - return dev_num; + if (!slave->dev_num_sticky) { + mutex_lock(&slave->bus->bus_lock); + dev_num = sdw_get_device_num(slave); + mutex_unlock(&slave->bus->bus_lock); + if (dev_num < 0) { + dev_err(slave->bus->dev, "Get dev_num failed: %d\n", + dev_num); + return dev_num; + } + slave->dev_num = dev_num; + slave->dev_num_sticky = dev_num; + new_device = true; + } else { + slave->dev_num = slave->dev_num_sticky; } - } else { + } + + if (!new_device) dev_info(slave->bus->dev, - "Slave already registered dev_num:%d\n", + "Slave already registered, reusing dev_num:%d\n", slave->dev_num); - /* Clear the slave->dev_num to transfer message on device 0 */ - dev_num = slave->dev_num; - slave->dev_num = 0; - } + /* Clear the slave->dev_num to transfer message on device 0 */ + dev_num = slave->dev_num; + slave->dev_num = 0; - ret = sdw_write(slave, SDW_SCP_DEVNUMBER, dev_num); + dev_dbg(slave->bus->dev, "in %s, writing dev_num %d\n", __func__, dev_num); + ret = sdw_write_no_pm(slave, SDW_SCP_DEVNUMBER, dev_num); if (ret < 0) { dev_err(&slave->dev, "Program device_num %d failed: %d\n", dev_num, ret); @@ -484,7 +544,7 @@ static int sdw_assign_device_num(struct sdw_slave *slave) } /* After xfer of msg, restore dev_num */ - slave->dev_num = dev_num; + slave->dev_num = slave->dev_num_sticky; return 0; } @@ -593,6 +653,8 @@ static int sdw_program_device_num(struct sdw_bus *bus) } while (ret == 0 && count < (SDW_MAX_DEVICES * 2)); + dev_err(bus->dev, "End of %s ret %d\n", __func__, ret); + return ret; } @@ -600,6 +662,26 @@ static void sdw_modify_slave_status(struct sdw_slave *slave, enum sdw_slave_status status) { mutex_lock(&slave->bus->bus_lock); + + dev_vdbg(&slave->dev, + "%s: changing status slave %d status %d new status %d\n", + __func__, slave->dev_num, slave->status, status); + + if (status == SDW_SLAVE_UNATTACHED) { + dev_dbg(&slave->dev, + "%s: initializing completion for Slave %d\n", + __func__, slave->dev_num); + + init_completion(&slave->enumeration_complete); + init_completion(&slave->initialization_complete); + + } else if (status == SDW_SLAVE_ATTACHED) { + dev_dbg(&slave->dev, + "%s: signaling completion for Slave %d\n", + __func__, slave->dev_num); + + complete(&slave->enumeration_complete); + } slave->status = status; mutex_unlock(&slave->bus->bus_lock); } @@ -960,10 +1042,29 @@ static int sdw_handle_slave_alerts(struct sdw_slave *slave) static int sdw_update_slave_status(struct sdw_slave *slave, enum sdw_slave_status status) { - if (slave->ops && slave->ops->update_status) - return slave->ops->update_status(slave, status); + unsigned long time; - return 0; + if (!slave->probed) { + /* + * the slave status update is typically handled in an + * interrupt thread, which can race with the driver + * probe, e.g. when a module needs to be loaded. + * + * make sure the probe is complete before updating + * status. + */ + time = wait_for_completion_timeout(&slave->probe_complete, + msecs_to_jiffies(DEFAULT_PROBE_TIMEOUT)); + if (!time) { + dev_err(&slave->dev, "Probe not complete, timed out\n"); + return -ETIMEDOUT; + } + } + + if (!slave->ops || !slave->ops->update_status) + return 0; + + return slave->ops->update_status(slave, status); } /** @@ -976,8 +1077,29 @@ int sdw_handle_slave_status(struct sdw_bus *bus, { enum sdw_slave_status prev_status; struct sdw_slave *slave; + bool attached_initializing; int i, ret = 0; + dev_dbg(bus->dev, "%s: start\n", __func__); + + /* first check if any Slaves fell off the bus */ + for (i = 1; i <= SDW_MAX_DEVICES; i++) { + mutex_lock(&bus->bus_lock); + if (test_bit(i, bus->assigned) == false) { + mutex_unlock(&bus->bus_lock); + continue; + } + mutex_unlock(&bus->bus_lock); + + slave = sdw_get_slave(bus, i); + if (!slave) + continue; + + if (status[i] == SDW_SLAVE_UNATTACHED && + slave->status != SDW_SLAVE_UNATTACHED) + sdw_modify_slave_status(slave, SDW_SLAVE_UNATTACHED); + } + if (status[0] == SDW_SLAVE_ATTACHED) { dev_dbg(bus->dev, "Slave attached, programming device number\n"); ret = sdw_program_device_num(bus); @@ -1003,6 +1125,8 @@ int sdw_handle_slave_status(struct sdw_bus *bus, if (!slave) continue; + attached_initializing = false; + switch (status[i]) { case SDW_SLAVE_UNATTACHED: if (slave->status == SDW_SLAVE_UNATTACHED) @@ -1020,14 +1144,19 @@ int sdw_handle_slave_status(struct sdw_bus *bus, break; case SDW_SLAVE_ATTACHED: - if (slave->status == SDW_SLAVE_ATTACHED) + if (slave->status == SDW_SLAVE_ATTACHED) { + dev_err(bus->dev, "Slave %d status attached, was already attached, ignoring\n", i); break; - + } prev_status = slave->status; sdw_modify_slave_status(slave, SDW_SLAVE_ATTACHED); - if (prev_status == SDW_SLAVE_ALERT) + if (prev_status == SDW_SLAVE_ALERT) { + dev_err(bus->dev, "Slave %d status attached, was alert, ignoring\n", i); break; + } + + attached_initializing = true; ret = sdw_initialize_slave(slave); if (ret) @@ -1043,12 +1172,48 @@ int sdw_handle_slave_status(struct sdw_bus *bus, break; } + dev_err(bus->dev, "Updating Slave %d status\n", i); ret = sdw_update_slave_status(slave, status[i]); if (ret) dev_err(slave->bus->dev, "Update Slave status failed:%d\n", ret); + + if (attached_initializing) + complete(&slave->initialization_complete); + + dev_err(bus->dev, "%s: Updating Slave %d status done\n", + __func__, i); } + dev_dbg(bus->dev, "%s: end\n", __func__); + return ret; } EXPORT_SYMBOL(sdw_handle_slave_status); + +void sdw_clear_slave_status(struct sdw_bus *bus, int request) +{ + struct sdw_slave *slave; + int i; + + /* Check all non-zero devices */ + for (i = 1; i <= SDW_MAX_DEVICES; i++) { + mutex_lock(&bus->bus_lock); + if (test_bit(i, bus->assigned) == false) { + mutex_unlock(&bus->bus_lock); + continue; + } + mutex_unlock(&bus->bus_lock); + + slave = sdw_get_slave(bus, i); + if (!slave) + continue; + + if (slave->status != SDW_SLAVE_UNATTACHED) + sdw_modify_slave_status(slave, SDW_SLAVE_UNATTACHED); + + /* keep track of request, used in pm_runtime resume */ + slave->unattach_request = request; + } +} +EXPORT_SYMBOL(sdw_clear_slave_status); diff --git a/drivers/soundwire/bus.h b/drivers/soundwire/bus.h index cb482da914da94..7cdd8f8a4d2c0d 100644 --- a/drivers/soundwire/bus.h +++ b/drivers/soundwire/bus.h @@ -5,6 +5,7 @@ #define __SDW_BUS_H #define DEFAULT_BANK_SWITCH_TIMEOUT 3000 +#define DEFAULT_PROBE_TIMEOUT 2000 #if IS_ENABLED(CONFIG_ACPI) int sdw_acpi_find_slaves(struct sdw_bus *bus); @@ -65,6 +66,7 @@ struct sdw_msg { }; #define SDW_DOUBLE_RATE_FACTOR 2 +#define SDW_STRM_RATE_GROUPING 1 extern int sdw_rows[SDW_FRAME_ROWS]; extern int sdw_cols[SDW_FRAME_COLS]; @@ -150,9 +152,50 @@ int sdw_transfer_defer(struct sdw_bus *bus, struct sdw_msg *msg, int sdw_fill_msg(struct sdw_msg *msg, struct sdw_slave *slave, u32 addr, size_t count, u16 dev_num, u8 flags, u8 *buf); +/* Retrieve and return channel count from channel mask */ +static inline int sdw_ch_mask_to_ch(int ch_mask) +{ + int c = 0; + + for (c = 0; ch_mask; ch_mask >>= 1) + c += ch_mask & 1; + + return c; +} + +/* Fill transport parameter data structure */ +static inline void sdw_fill_xport_params(struct sdw_transport_params *params, + int port_num, bool grp_ctrl_valid, + int grp_ctrl, int sample_int, + int off1, int off2, + int hstart, int hstop, + int pack_mode, int lane_ctrl) +{ + params->port_num = port_num; + params->blk_grp_ctrl_valid = grp_ctrl_valid; + params->blk_grp_ctrl = grp_ctrl; + params->sample_interval = sample_int; + params->offset1 = off1; + params->offset2 = off2; + params->hstart = hstart; + params->hstop = hstop; + params->blk_pkg_mode = pack_mode; + params->lane_ctrl = lane_ctrl; +} + +/* Fill port parameter data structure */ +static inline void sdw_fill_port_params(struct sdw_port_params *params, + int port_num, int bps, + int flow_mode, int data_mode) +{ + params->num = port_num; + params->bps = bps; + params->flow_mode = flow_mode; + params->data_mode = data_mode; +} + /* Read-Modify-Write Slave register */ -static inline int -sdw_update(struct sdw_slave *slave, u32 addr, u8 mask, u8 val) +static inline int sdw_update(struct sdw_slave *slave, u32 addr, u8 mask, u8 val) { int tmp; @@ -164,4 +207,11 @@ sdw_update(struct sdw_slave *slave, u32 addr, u8 mask, u8 val) return sdw_write(slave, addr, tmp); } +/* possible requests to force Slaves to become unattached */ +#define SDW_UNATTACH_REQUEST_MASTER_RESET BIT(0) +#define SDW_UNATTACH_REQUEST_MASTER_RESUME BIT(1) +#define SDW_UNATTACH_REQUEST_MASTER_PM_RESUME BIT(2) + +void sdw_clear_slave_status(struct sdw_bus *bus, int request); + #endif /* __SDW_BUS_H */ diff --git a/drivers/soundwire/bus_type.c b/drivers/soundwire/bus_type.c index 4a465f55039f4b..ac484384bd6e75 100644 --- a/drivers/soundwire/bus_type.c +++ b/drivers/soundwire/bus_type.c @@ -33,8 +33,8 @@ sdw_get_device_id(struct sdw_slave *slave, struct sdw_driver *drv) static int sdw_bus_match(struct device *dev, struct device_driver *ddrv) { - struct sdw_slave *slave = dev_to_sdw_dev(dev); - struct sdw_driver *drv = drv_to_sdw_driver(ddrv); + struct sdw_slave *slave = to_sdw_slave_device(dev); + struct sdw_driver *drv = to_sdw_slave_driver(ddrv); return !!sdw_get_device_id(slave, drv); } @@ -49,13 +49,21 @@ int sdw_slave_modalias(const struct sdw_slave *slave, char *buf, size_t size) static int sdw_uevent(struct device *dev, struct kobj_uevent_env *env) { - struct sdw_slave *slave = dev_to_sdw_dev(dev); + struct sdw_slave *slave; char modalias[32]; - sdw_slave_modalias(slave, modalias, sizeof(modalias)); + if (is_sdw_slave(dev)) { + slave = to_sdw_slave_device(dev); - if (add_uevent_var(env, "MODALIAS=%s", modalias)) - return -ENOMEM; + sdw_slave_modalias(slave, modalias, sizeof(modalias)); + + if (add_uevent_var(env, "MODALIAS=%s", modalias)) + return -ENOMEM; + } else if (!is_sdw_md(dev)) { + /* only Slave and Master device type supported */ + dev_warn(dev, "uevent for unknown Soundwire type\n"); + return -EINVAL; + } return 0; } @@ -67,10 +75,10 @@ struct bus_type sdw_bus_type = { }; EXPORT_SYMBOL_GPL(sdw_bus_type); -static int sdw_drv_probe(struct device *dev) +static int sdw_slave_drv_probe(struct device *dev) { - struct sdw_slave *slave = dev_to_sdw_dev(dev); - struct sdw_driver *drv = drv_to_sdw_driver(dev->driver); + struct sdw_slave *slave = to_sdw_slave_device(dev); + struct sdw_driver *drv = to_sdw_slave_driver(dev->driver); const struct sdw_device_id *id; int ret; @@ -110,13 +118,18 @@ static int sdw_drv_probe(struct device *dev) slave->bus->clk_stop_timeout = max_t(u32, slave->bus->clk_stop_timeout, slave->prop.clk_stop_timeout); + slave->probed = true; + complete(&slave->probe_complete); + + dev_dbg(dev, "probe complete\n"); + return 0; } -static int sdw_drv_remove(struct device *dev) +static int sdw_slave_drv_remove(struct device *dev) { - struct sdw_slave *slave = dev_to_sdw_dev(dev); - struct sdw_driver *drv = drv_to_sdw_driver(dev->driver); + struct sdw_slave *slave = to_sdw_slave_device(dev); + struct sdw_driver *drv = to_sdw_slave_driver(dev->driver); int ret = 0; if (drv->remove) @@ -127,23 +140,24 @@ static int sdw_drv_remove(struct device *dev) return ret; } -static void sdw_drv_shutdown(struct device *dev) +static void sdw_slave_drv_shutdown(struct device *dev) { - struct sdw_slave *slave = dev_to_sdw_dev(dev); - struct sdw_driver *drv = drv_to_sdw_driver(dev->driver); + struct sdw_slave *slave = to_sdw_slave_device(dev); + struct sdw_driver *drv = to_sdw_slave_driver(dev->driver); if (drv->shutdown) drv->shutdown(slave); } /** - * __sdw_register_driver() - register a SoundWire Slave driver + * __sdw_register_slave_driver() - register a SoundWire Slave driver * @drv: driver to register * @owner: owning module/driver * * Return: zero on success, else a negative error code. */ -int __sdw_register_driver(struct sdw_driver *drv, struct module *owner) +int __sdw_register_slave_driver(struct sdw_driver *drv, + struct module *owner) { drv->driver.bus = &sdw_bus_type; @@ -154,27 +168,27 @@ int __sdw_register_driver(struct sdw_driver *drv, struct module *owner) } drv->driver.owner = owner; - drv->driver.probe = sdw_drv_probe; + drv->driver.probe = sdw_slave_drv_probe; if (drv->remove) - drv->driver.remove = sdw_drv_remove; + drv->driver.remove = sdw_slave_drv_remove; if (drv->shutdown) - drv->driver.shutdown = sdw_drv_shutdown; + drv->driver.shutdown = sdw_slave_drv_shutdown; return driver_register(&drv->driver); } -EXPORT_SYMBOL_GPL(__sdw_register_driver); +EXPORT_SYMBOL_GPL(__sdw_register_slave_driver); /** - * sdw_unregister_driver() - unregisters the SoundWire Slave driver + * sdw_unregister_slave_driver() - unregisters the SoundWire Slave driver * @drv: driver to unregister */ -void sdw_unregister_driver(struct sdw_driver *drv) +void sdw_unregister_slave_driver(struct sdw_driver *drv) { driver_unregister(&drv->driver); } -EXPORT_SYMBOL_GPL(sdw_unregister_driver); +EXPORT_SYMBOL_GPL(sdw_unregister_slave_driver); static int __init sdw_bus_init(void) { diff --git a/drivers/soundwire/cadence_master.c b/drivers/soundwire/cadence_master.c index fed21e2b22774d..d7ecd6ffcbccfe 100644 --- a/drivers/soundwire/cadence_master.c +++ b/drivers/soundwire/cadence_master.c @@ -17,6 +17,7 @@ #include #include #include +#include #include "bus.h" #include "cadence_master.h" @@ -195,6 +196,7 @@ static inline u32 cdns_readl(struct sdw_cdns *cdns, int offset) static inline void cdns_writel(struct sdw_cdns *cdns, int offset, u32 value) { + dev_vdbg(cdns->dev, "%s %x %x\n", __func__, offset, value); writel(value, cdns->registers + offset); } @@ -672,13 +674,36 @@ static int cdns_update_slave_status(struct sdw_cdns *cdns, /* first check if Slave reported multiple status */ if (set_status > 1) { + u32 val; + + dev_warn_ratelimited(cdns->dev, + "Slave %d reported multiple Status: %d\n", + i, mask); + + /* check latest status extracted from PING commands */ + val = cdns_readl(cdns, CDNS_MCP_SLAVE_STAT); + val >>= (i * 2); + + switch (val & 0x3) { + case 0: + status[i] = SDW_SLAVE_UNATTACHED; + break; + case 1: + status[i] = SDW_SLAVE_ATTACHED; + break; + case 2: + status[i] = SDW_SLAVE_ALERT; + break; + case 3: + default: + status[i] = SDW_SLAVE_RESERVED; + break; + } + dev_warn_ratelimited(cdns->dev, - "Slave reported multiple Status: %d\n", - mask); - /* - * TODO: we need to reread the status here by - * issuing a PING cmd - */ + "Slave %d status updated to %d\n", + i, status[i]); + } } @@ -745,7 +770,7 @@ irqreturn_t sdw_cdns_irq(int irq, void *dev_id) CDNS_MCP_INT_SLAVE_MASK, 0); int_status &= ~CDNS_MCP_INT_SLAVE_MASK; - ret = IRQ_WAKE_THREAD; + schedule_work(&cdns->work); } cdns_writel(cdns, CDNS_MCP_INTSTAT, int_status); @@ -754,13 +779,14 @@ irqreturn_t sdw_cdns_irq(int irq, void *dev_id) EXPORT_SYMBOL(sdw_cdns_irq); /** - * sdw_cdns_thread() - Cadence irq thread handler - * @irq: irq number - * @dev_id: irq context + * To update slave status in a work since we will need to handle + * other interrupts eg. CDNS_MCP_INT_RX_WL during the update slave + * process. */ -irqreturn_t sdw_cdns_thread(int irq, void *dev_id) +static void cdns_update_slave_status_work(struct work_struct *work) { - struct sdw_cdns *cdns = dev_id; + struct sdw_cdns *cdns = + container_of(work, struct sdw_cdns, work); u32 slave0, slave1; dev_dbg_ratelimited(cdns->dev, "Slave status change\n"); @@ -777,9 +803,7 @@ irqreturn_t sdw_cdns_thread(int irq, void *dev_id) cdns_updatel(cdns, CDNS_MCP_INTMASK, CDNS_MCP_INT_SLAVE_MASK, CDNS_MCP_INT_SLAVE_MASK); - return IRQ_HANDLED; } -EXPORT_SYMBOL(sdw_cdns_thread); /* * init routines @@ -1187,6 +1211,7 @@ int sdw_cdns_probe(struct sdw_cdns *cdns) init_completion(&cdns->tx_complete); cdns->bus.port_ops = &cdns_port_ops; + INIT_WORK(&cdns->work, cdns_update_slave_status_work); return 0; } EXPORT_SYMBOL(sdw_cdns_probe); diff --git a/drivers/soundwire/cadence_master.h b/drivers/soundwire/cadence_master.h index 001457cbe5ad2e..0f108fd31fd943 100644 --- a/drivers/soundwire/cadence_master.h +++ b/drivers/soundwire/cadence_master.h @@ -126,6 +126,10 @@ struct sdw_cdns { bool link_up; unsigned int msg_count; + + struct work_struct work; + + struct list_head list; }; #define bus_to_cdns(_bus) container_of(_bus, struct sdw_cdns, bus) diff --git a/drivers/soundwire/generic_bandwidth_allocation.c b/drivers/soundwire/generic_bandwidth_allocation.c new file mode 100644 index 00000000000000..6124c6f8561f32 --- /dev/null +++ b/drivers/soundwire/generic_bandwidth_allocation.c @@ -0,0 +1,407 @@ +// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause) +// Copyright(c) 2015-2019 Intel Corporation. + +/* + * Bandwidth management algorithm based on 2^n gears + * + */ + +#include +#include +#include +#include +#include +#include "bus.h" + +#define SDW_STRM_RATE_GROUPING 1 + +struct sdw_group_params { + unsigned int rate; + int full_bw; + int payload_bw; + int hwidth; +}; + +struct sdw_group { + unsigned int count; + unsigned int max_size; + unsigned int *rates; +}; + +struct sdw_transport_data { + int hstart; + int hstop; + int block_offset; + int sub_block_offset; +}; + +static void sdw_compute_slave_ports(struct sdw_master_runtime *m_rt, + struct sdw_transport_data *t_data) +{ + struct sdw_slave_runtime *s_rt = NULL; + struct sdw_port_runtime *p_rt; + int port_bo, sample_int; + unsigned int rate, bps, ch = 0; + + port_bo = t_data->block_offset; + + list_for_each_entry(s_rt, &m_rt->slave_rt_list, m_rt_node) { + rate = m_rt->stream->params.rate; + bps = m_rt->stream->params.bps; + sample_int = (m_rt->bus->params.curr_dr_freq / rate); + + list_for_each_entry(p_rt, &s_rt->port_list, port_node) { + ch = sdw_ch_mask_to_ch(p_rt->ch_mask); + + sdw_fill_xport_params(&p_rt->transport_params, + p_rt->num, false, + SDW_BLK_GRP_CNT_1, + sample_int, port_bo, port_bo >> 8, + t_data->hstart, + t_data->hstop, + (SDW_BLK_GRP_CNT_1 * ch), 0x0); + + sdw_fill_port_params(&p_rt->port_params, + p_rt->num, bps, + SDW_PORT_FLOW_MODE_ISOCH, + SDW_PORT_DATA_MODE_NORMAL); + + port_bo += bps * ch; + } + } +} + +static void sdw_compute_master_ports(struct sdw_master_runtime *m_rt, + struct sdw_group_params *params, + int port_bo, int hstop) +{ + struct sdw_transport_data t_data = {0}; + struct sdw_port_runtime *p_rt; + struct sdw_bus *bus = m_rt->bus; + int sample_int, hstart = 0; + unsigned int rate, bps, ch, no_ch; + + rate = m_rt->stream->params.rate; + bps = m_rt->stream->params.bps; + ch = m_rt->ch_count; + sample_int = (bus->params.curr_dr_freq / rate); + + if (rate != params->rate) + return; + + t_data.hstop = hstop; + hstart = hstop - params->hwidth + 1; + t_data.hstart = hstart; + + list_for_each_entry(p_rt, &m_rt->port_list, port_node) { + no_ch = sdw_ch_mask_to_ch(p_rt->ch_mask); + + sdw_fill_xport_params(&p_rt->transport_params, p_rt->num, + false, SDW_BLK_GRP_CNT_1, sample_int, + port_bo, port_bo >> 8, hstart, hstop, + (SDW_BLK_GRP_CNT_1 * no_ch), 0x0); + + sdw_fill_port_params(&p_rt->port_params, + p_rt->num, bps, + SDW_PORT_FLOW_MODE_ISOCH, + SDW_PORT_DATA_MODE_NORMAL); + + /* Check for first entry */ + if (!(p_rt == list_first_entry(&m_rt->port_list, + struct sdw_port_runtime, + port_node))) { + port_bo += bps * ch; + continue; + } + + t_data.hstart = hstart; + t_data.hstop = hstop; + t_data.block_offset = port_bo; + t_data.sub_block_offset = 0; + port_bo += bps * ch; + } + + sdw_compute_slave_ports(m_rt, &t_data); +} + +static void _sdw_compute_port_params(struct sdw_bus *bus, + struct sdw_group_params *params, int count) +{ + struct sdw_master_runtime *m_rt = NULL; + int hstop = bus->params.col - 1; + int block_offset, port_bo, i; + + /* Run loop for all groups to compute transport parameters */ + for (i = 0; i < count; i++) { + port_bo = 1; + block_offset = 1; + + list_for_each_entry(m_rt, &bus->m_rt_list, bus_node) { + sdw_compute_master_ports(m_rt, ¶ms[i], + port_bo, hstop); + + block_offset += m_rt->ch_count * + m_rt->stream->params.bps; + port_bo = block_offset; + } + + hstop = hstop - params[i].hwidth; + } +} + +static int sdw_compute_group_params(struct sdw_bus *bus, + struct sdw_group_params *params, + int *rates, int count) +{ + struct sdw_master_runtime *m_rt = NULL; + int sel_col = bus->params.col; + unsigned int rate, bps, ch; + int i, column_needed = 0; + + /* Calculate bandwidth per group */ + for (i = 0; i < count; i++) { + params[i].rate = rates[i]; + params[i].full_bw = bus->params.curr_dr_freq / params[i].rate; + } + + list_for_each_entry(m_rt, &bus->m_rt_list, bus_node) { + rate = m_rt->stream->params.rate; + bps = m_rt->stream->params.bps; + ch = m_rt->ch_count; + + for (i = 0; i < count; i++) { + if (rate == params[i].rate) + params[i].payload_bw += bps * ch; + } + } + + for (i = 0; i < count; i++) { + params[i].hwidth = (sel_col * + params[i].payload_bw + params[i].full_bw - 1) / + params[i].full_bw; + + column_needed += params[i].hwidth; + } + + if (column_needed > sel_col - 1) + return -EINVAL; + + return 0; +} + +static int sdw_add_element_group_count(struct sdw_group *group, + unsigned int rate) +{ + int num = group->count; + int i; + + for (i = 0; i <= num; i++) { + if (rate == group->rates[i]) + break; + + if (i != num) + continue; + + if (group->count >= group->max_size) { + group->max_size += 1; + group->rates = krealloc(group->rates, + (sizeof(int) * group->max_size), + GFP_KERNEL); + if (!group->rates) + return -ENOMEM; + } + + group->rates[group->count++] = rate; + } + + return 0; +} + +static int sdw_get_group_count(struct sdw_bus *bus, + struct sdw_group *group) +{ + struct sdw_master_runtime *m_rt; + unsigned int rate; + int ret = 0; + + group->count = 0; + group->max_size = SDW_STRM_RATE_GROUPING; + group->rates = kcalloc(group->max_size, sizeof(int), GFP_KERNEL); + if (!group->rates) + return -ENOMEM; + + list_for_each_entry(m_rt, &bus->m_rt_list, bus_node) { + rate = m_rt->stream->params.rate; + if (m_rt == list_first_entry(&bus->m_rt_list, + struct sdw_master_runtime, + bus_node)) { + group->rates[group->count++] = rate; + + } else { + ret = sdw_add_element_group_count(group, rate); + if (ret < 0) + return ret; + } + } + + return ret; +} + +/** + * sdw_compute_port_params: Compute transport and port parameters + * + * @bus: SDW Bus instance + */ +static int sdw_compute_port_params(struct sdw_bus *bus) +{ + struct sdw_group_params *params = NULL; + struct sdw_group group; + int ret; + + ret = sdw_get_group_count(bus, &group); + if (ret < 0) + goto out; + + if (group.count == 0) + goto out; + + params = kcalloc(group.count, sizeof(*params), GFP_KERNEL); + if (!params) { + ret = -ENOMEM; + goto out; + } + + /* Compute transport parameters for grouped streams */ + ret = sdw_compute_group_params(bus, params, + &group.rates[0], group.count); + if (ret < 0) + goto out; + + _sdw_compute_port_params(bus, params, group.count); + +out: + kfree(params); + kfree(group.rates); + + return ret; +} + +static int sdw_select_row_col(struct sdw_bus *bus, int clk_freq) +{ + struct sdw_master_prop *prop = &bus->prop; + int frame_int, frame_freq; + int r, c; + + for (c = 0; c < SDW_FRAME_COLS; c++) { + for (r = 0; r < SDW_FRAME_ROWS; r++) { + if (sdw_rows[r] != prop->default_row || + sdw_cols[c] != prop->default_col) + continue; + + frame_int = sdw_rows[r] * sdw_cols[c]; + frame_freq = clk_freq / frame_int; + + if ((clk_freq - (frame_freq * SDW_FRAME_CTRL_BITS)) < + bus->params.bandwidth) + continue; + + bus->params.row = sdw_rows[r]; + bus->params.col = sdw_cols[c]; + return 0; + } + } + + return -EINVAL; +} + +/** + * sdw_compute_bus_params: Compute bus parameters + * + * @bus: SDW Bus instance + */ +static int sdw_compute_bus_params(struct sdw_bus *bus) +{ + unsigned int max_dr_freq, curr_dr_freq = 0; + struct sdw_master_prop *mstr_prop = NULL; + int i, clk_values, ret; + bool is_gear = false; + u32 *clk_buf; + + mstr_prop = &bus->prop; + if (!mstr_prop) + return -EINVAL; + + if (mstr_prop->num_clk_gears) { + clk_values = mstr_prop->num_clk_gears; + clk_buf = mstr_prop->clk_gears; + is_gear = true; + } else if (mstr_prop->num_clk_freq) { + clk_values = mstr_prop->num_clk_freq; + clk_buf = mstr_prop->clk_freq; + } else { + clk_values = 1; + clk_buf = NULL; + } + + max_dr_freq = mstr_prop->max_clk_freq * SDW_DOUBLE_RATE_FACTOR; + + for (i = 0; i < clk_values; i++) { + if (!clk_buf) + curr_dr_freq = max_dr_freq; + else + curr_dr_freq = (is_gear) ? + (max_dr_freq >> clk_buf[i]) : + clk_buf[i] * SDW_DOUBLE_RATE_FACTOR; + + if (curr_dr_freq <= bus->params.bandwidth) + continue; + + break; + + /* + * TODO: Check all the Slave(s) port(s) audio modes and find + * whether given clock rate is supported with glitchless + * transition. + */ + } + + if (i == clk_values) + return -EINVAL; + + ret = sdw_select_row_col(bus, curr_dr_freq); + if (ret < 0) + return -EINVAL; + + bus->params.curr_dr_freq = curr_dr_freq; + return 0; +} + +/** + * sdw_compute_params: Compute bus, transport and port parameters + * + * @bus: SDW Bus instance + */ +int sdw_compute_params(struct sdw_bus *bus) +{ + int ret; + + /* Computes clock frequency, frame shape and frame frequency */ + ret = sdw_compute_bus_params(bus); + if (ret < 0) { + dev_err(bus->dev, "Compute bus params failed: %d", ret); + return ret; + } + + /* Compute transport and port params */ + ret = sdw_compute_port_params(bus); + if (ret < 0) { + dev_err(bus->dev, "Compute transport params failed: %d", ret); + return ret; + } + + return 0; +} +EXPORT_SYMBOL(sdw_compute_params); + +MODULE_LICENSE("Dual BSD/GPL"); +MODULE_DESCRIPTION("SoundWire Generic Bandwidth Allocation"); diff --git a/drivers/soundwire/intel.c b/drivers/soundwire/intel.c index 585a83fad75de1..64fd0e94bac0ac 100644 --- a/drivers/soundwire/intel.c +++ b/drivers/soundwire/intel.c @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -21,6 +22,21 @@ #include "bus.h" #include "intel.h" +/* + * 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) + +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 @@ -88,12 +104,10 @@ #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_VAL 0xf #define SDW_ALH_STRMZCFG_DMAT GENMASK(7, 0) #define SDW_ALH_STRMZCFG_CHN GENMASK(19, 16) -#define SDW_INTEL_QUIRK_MASK_BUS_DISABLE BIT(1) - enum intel_pdi_type { INTEL_PDI_IN = 0, INTEL_PDI_OUT = 1, @@ -103,7 +117,9 @@ enum intel_pdi_type { struct sdw_intel { struct sdw_cdns cdns; int instance; - struct sdw_intel_link_res *res; + struct sdw_intel_link_res *link_res; + struct snd_pcm_hw_params *hw_params; + struct sdw_cdns_pdi *pdi; #ifdef CONFIG_DEBUG_FS struct dentry *debugfs; #endif @@ -193,8 +209,8 @@ static ssize_t intel_sprintf(void __iomem *mem, bool l, static int intel_reg_show(struct seq_file *s_file, void *data) { struct sdw_intel *sdw = s_file->private; - void __iomem *s = sdw->res->shim; - void __iomem *a = sdw->res->alh; + void __iomem *s = sdw->link_res->shim; + void __iomem *a = sdw->link_res->alh; char *buf; ssize_t ret; int i, j; @@ -289,7 +305,7 @@ static void intel_debugfs_exit(struct sdw_intel *sdw) {} static int intel_link_power_up(struct sdw_intel *sdw) { unsigned int link_id = sdw->instance; - void __iomem *shim = sdw->res->shim; + void __iomem *shim = sdw->link_res->shim; int spa_mask, cpa_mask; int link_control, ret; @@ -309,7 +325,7 @@ static int intel_link_power_up(struct sdw_intel *sdw) static int intel_shim_init(struct sdw_intel *sdw) { - void __iomem *shim = sdw->res->shim; + void __iomem *shim = sdw->link_res->shim; unsigned int link_id = sdw->instance; int sync_reg, ret; u16 ioctl = 0, act = 0; @@ -364,13 +380,66 @@ static int intel_shim_init(struct sdw_intel *sdw) return ret; } +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; + + if (wake_enable) { + /* Enable the wakeup */ + intel_writew(shim, SDW_SHIM_WAKEEN, + (SDW_SHIM_WAKEEN_ENABLE << link_id)); + } else { + /* Disable the wake up interrupt */ + wake_en = intel_readw(shim, SDW_SHIM_WAKEEN); + 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_WAKEEN_ENABLE << link_id); + intel_writew(shim, SDW_SHIM_WAKESTS_STATUS, wake_sts); + } +} + +static int intel_link_power_down(struct sdw_intel *sdw) +{ + int link_control, spa_mask, cpa_mask, ret; + 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); + + ioctl &= ~(SDW_SHIM_IOCTL_MIF); + intel_writew(shim, SDW_SHIM_IOCTL(link_id), ioctl); + + /* Link power down sequence */ + link_control = intel_readl(shim, SDW_SHIM_LCTL); + spa_mask = ~(SDW_SHIM_LCTL_SPA << link_id); + cpa_mask = (SDW_SHIM_LCTL_CPA << link_id); + link_control &= spa_mask; + + ret = intel_clear_bit(shim, SDW_SHIM_LCTL, link_control, cpa_mask); + if (ret < 0) + return ret; + + sdw->cdns.link_up = false; + return 0; +} + /* * PDI routines */ static void intel_pdi_init(struct sdw_intel *sdw, struct sdw_cdns_stream_config *config) { - void __iomem *shim = sdw->res->shim; + void __iomem *shim = sdw->link_res->shim; unsigned int link_id = sdw->instance; int pcm_cap, pdm_cap; @@ -404,7 +473,7 @@ static void intel_pdi_init(struct sdw_intel *sdw, static int intel_pdi_get_ch_cap(struct sdw_intel *sdw, unsigned int pdi_num, bool pcm) { - void __iomem *shim = sdw->res->shim; + void __iomem *shim = sdw->link_res->shim; unsigned int link_id = sdw->instance; int count; @@ -476,11 +545,14 @@ static int intel_pdi_ch_update(struct sdw_intel *sdw) static void intel_pdi_shim_configure(struct sdw_intel *sdw, struct sdw_cdns_pdi *pdi) { - void __iomem *shim = sdw->res->shim; + void __iomem *shim = sdw->link_res->shim; unsigned int link_id = sdw->instance; int pdi_conf = 0; - pdi->intel_alh_id = (link_id * 16) + pdi->num + 5; + /* the Bulk and PCM streams are not contiguous */ + pdi->intel_alh_id = (link_id * 16) + pdi->num + 3; + if (pdi->num >= 2) + pdi->intel_alh_id += 2; /* * Program stream parameters to stream SHIM register @@ -505,11 +577,14 @@ intel_pdi_shim_configure(struct sdw_intel *sdw, struct sdw_cdns_pdi *pdi) static void intel_pdi_alh_configure(struct sdw_intel *sdw, struct sdw_cdns_pdi *pdi) { - void __iomem *alh = sdw->res->alh; + void __iomem *alh = sdw->link_res->alh; unsigned int link_id = sdw->instance; unsigned int conf; - pdi->intel_alh_id = (link_id * 16) + pdi->num + 5; + /* the Bulk and PCM streams are not contiguous */ + pdi->intel_alh_id = (link_id * 16) + pdi->num + 3; + if (pdi->num >= 2) + pdi->intel_alh_id += 2; /* Program Stream config ALH register */ conf = intel_readl(alh, SDW_ALH_STRMZCFG(pdi->intel_alh_id)); @@ -523,18 +598,44 @@ intel_pdi_alh_configure(struct sdw_intel *sdw, struct sdw_cdns_pdi *pdi) intel_writel(alh, SDW_ALH_STRMZCFG(pdi->intel_alh_id), conf); } -static int intel_config_stream(struct sdw_intel *sdw, +static int intel_params_stream(struct sdw_intel *sdw, struct snd_pcm_substream *substream, struct snd_soc_dai *dai, - struct snd_pcm_hw_params *hw_params, int link_id) + struct snd_pcm_hw_params *hw_params, + int link_id, int alh_stream_id) { - struct sdw_intel_link_res *res = sdw->res; + struct sdw_intel_link_res *res = sdw->link_res; + struct sdw_intel_stream_params_data params_data; + + params_data.substream = substream; + params_data.dai = dai; + params_data.hw_params = hw_params; + params_data.link_id = link_id; + params_data.alh_stream_id = alh_stream_id; + + if (res->ops && res->ops->params_stream && res->dev) + return res->ops->params_stream(res->dev, + ¶ms_data); + return -EIO; +} + +static int intel_free_stream(struct sdw_intel *sdw, + struct snd_pcm_substream *substream, + struct snd_soc_dai *dai, + int link_id) +{ + struct sdw_intel_link_res *res = sdw->link_res; + struct sdw_intel_stream_free_data free_data; - if (res->ops && res->ops->config_stream && res->arg) - return res->ops->config_stream(res->arg, - substream, dai, hw_params, link_id); + free_data.substream = substream; + free_data.dai = dai; + free_data.link_id = link_id; - return -EIO; + if (res->ops && res->ops->free_stream && res->dev) + return res->ops->free_stream(res->dev, + &free_data); + + return 0; } /* @@ -545,7 +646,7 @@ static int intel_pre_bank_switch(struct sdw_bus *bus) { struct sdw_cdns *cdns = bus_to_cdns(bus); struct sdw_intel *sdw = cdns_to_intel(cdns); - void __iomem *shim = sdw->res->shim; + void __iomem *shim = sdw->link_res->shim; int sync_reg; /* Write to register only for multi-link */ @@ -564,7 +665,7 @@ static int intel_post_bank_switch(struct sdw_bus *bus) { struct sdw_cdns *cdns = bus_to_cdns(bus); struct sdw_intel *sdw = cdns_to_intel(cdns); - void __iomem *shim = sdw->res->shim; + void __iomem *shim = sdw->link_res->shim; int sync_reg, ret; /* Write to register only for multi-link */ @@ -604,6 +705,81 @@ static int intel_post_bank_switch(struct sdw_bus *bus) * DAI routines */ +static int sdw_stream_setup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct sdw_stream_runtime *sdw_stream = NULL; + char *name; + int i, ret; + + name = kzalloc(32, GFP_KERNEL); + if (!name) + return -ENOMEM; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + snprintf(name, 32, "%s-Playback", dai->name); + else + snprintf(name, 32, "%s-Capture", dai->name); + + sdw_stream = sdw_alloc_stream(name); + if (!sdw_stream) { + dev_err(dai->dev, "alloc stream failed for DAI %s", dai->name); + ret = -ENOMEM; + goto error; + } + + /* Set stream pointer on CPU DAI */ + ret = snd_soc_dai_set_sdw_stream(dai, sdw_stream, substream->stream); + if (ret < 0) { + dev_err(dai->dev, "failed to set stream pointer on cpu dai %s", + dai->name); + goto release_stream; + } + + /* Set stream pointer on all CODEC DAIs */ + for (i = 0; i < rtd->num_codecs; i++) { + ret = snd_soc_dai_set_sdw_stream(rtd->codec_dais[i], sdw_stream, + substream->stream); + if (ret < 0) { + dev_err(dai->dev, "failed to set stream pointer on codec dai %s", + rtd->codec_dais[i]->name); + goto release_stream; + } + } + + return 0; + +release_stream: + sdw_release_stream(sdw_stream); +error: + kfree(name); + return ret; +} + +static int intel_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct sdw_cdns *cdns = snd_soc_dai_get_drvdata(dai); + int ret; + + dev_err(dai->dev, "%s: %s: start\n", __func__, dai->name); + + ret = pm_runtime_get_sync(cdns->dev); + if (ret < 0 && ret != -EACCES) { + dev_err_ratelimited(cdns->dev, + "pm_runtime_get_sync failed in %s, ret %d\n", + __func__, ret); + pm_runtime_put_noidle(cdns->dev); + } + + ret = sdw_stream_setup(substream, dai); + + dev_err(dai->dev, "%s: %s: done\n", __func__, dai->name); + + return ret; +} + static int intel_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) @@ -618,6 +794,8 @@ static int intel_hw_params(struct snd_pcm_substream *substream, int ret; bool pcm = true; + dev_err(dai->dev, "%s: %s: start\n", __func__, dai->name); + dma = snd_soc_dai_get_dma_data(dai, substream); if (!dma) return -EIO; @@ -646,9 +824,12 @@ static int intel_hw_params(struct snd_pcm_substream *substream, intel_pdi_alh_configure(sdw, pdi); sdw_cdns_config_stream(cdns, ch, dir, pdi); + sdw->pdi = pdi; + sdw->hw_params = params; /* Inform DSP about PDI stream number */ - ret = intel_config_stream(sdw, substream, dai, params, + ret = intel_params_stream(sdw, substream, dai, params, + sdw->instance, pdi->intel_alh_id); if (ret) goto error; @@ -682,6 +863,104 @@ static int intel_hw_params(struct snd_pcm_substream *substream, kfree(pconfig); error: + + dev_err(dai->dev, "%s: %s: done\n", __func__, dai->name); + return ret; +} + +static int intel_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct sdw_cdns *cdns = snd_soc_dai_get_drvdata(dai); + struct sdw_intel *sdw = cdns_to_intel(cdns); + struct sdw_cdns_dma_data *dma; + int ch, dir; + int ret; + + dev_err(dai->dev, "%s: %s: start\n", __func__, dai->name); + + dma = snd_soc_dai_get_dma_data(dai, substream); + if (!dma) { + dev_err(dai->dev, "failed to get dma data in %s", + __func__); + return -EIO; + } + + /* + * .prepare() is called after system resume, where we need to + * reinitialize the SHIM/ALH/Cadence IP. To avoid dealing with + * complicated state machines, we just re-initialize in all + * cases since there are no side effects. + */ + + /* configure stream */ + ch = params_channels(sdw->hw_params); + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + dir = SDW_DATA_DIR_RX; + else + dir = SDW_DATA_DIR_TX; + + intel_pdi_shim_configure(sdw, sdw->pdi); + intel_pdi_alh_configure(sdw, sdw->pdi); + sdw_cdns_config_stream(cdns, ch, dir, sdw->pdi); + + /* Inform DSP about PDI stream number */ + ret = intel_params_stream(sdw, substream, dai, sdw->hw_params, + sdw->instance, + sdw->pdi->intel_alh_id); + if (ret) + goto err; + + ret = sdw_prepare_stream(dma->stream); + + dev_err(dai->dev, "%s: %s: done\n", __func__, dai->name); +err: + return ret; +} + +static int intel_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + struct sdw_cdns_dma_data *dma; + int ret; + + dev_err(dai->dev, "%s: %s: start\n", __func__, dai->name); + + dma = snd_soc_dai_get_dma_data(dai, substream); + if (!dma) { + dev_err(dai->dev, "failed to get dma data in %s", __func__); + return -EIO; + } + + switch (cmd) { + case SNDRV_PCM_TRIGGER_RESUME: + dev_err(dai->dev, "%s: %s: resume\n", __func__, dai->name); + /* fallthrough */ + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + ret = sdw_enable_stream(dma->stream); + break; + + case SNDRV_PCM_TRIGGER_SUSPEND: + dev_err(dai->dev, "%s: %s: suspend\n", __func__, dai->name); + /* fallthrough */ + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + case SNDRV_PCM_TRIGGER_STOP: + ret = sdw_disable_stream(dma->stream); + break; + + default: + ret = -EINVAL; + break; + } + + if (ret) + dev_err(dai->dev, + "%s trigger %d failed: %d", + __func__, cmd, ret); + + dev_err(dai->dev, "%s: %s: done\n", __func__, dai->name); + return ret; } @@ -689,25 +968,51 @@ static int intel_hw_free(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { struct sdw_cdns *cdns = snd_soc_dai_get_drvdata(dai); + struct sdw_intel *sdw = cdns_to_intel(cdns); struct sdw_cdns_dma_data *dma; int ret; + dev_err(dai->dev, "%s: %s: start\n", __func__, dai->name); + dma = snd_soc_dai_get_dma_data(dai, substream); if (!dma) return -EIO; + ret = sdw_deprepare_stream(dma->stream); + if (ret) { + dev_err(dai->dev, "sdw_deprepare_stream: failed %d", ret); + return ret; + } + ret = sdw_stream_remove_master(&cdns->bus, dma->stream); - if (ret < 0) + if (ret < 0) { dev_err(dai->dev, "remove master from stream %s failed: %d\n", dma->stream->name, ret); + return ret; + } - return ret; + ret = intel_free_stream(sdw, substream, dai, sdw->instance); + if (ret < 0) { + dev_err(dai->dev, "intel_free_stream: failed %d", ret); + return ret; + } + + sdw->hw_params = NULL; + sdw->pdi = NULL; + sdw_release_stream(dma->stream); + + dev_err(dai->dev, "%s: %s: done\n", __func__, dai->name); + + return 0; } static void intel_shutdown(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { struct sdw_cdns_dma_data *dma; + struct sdw_cdns *cdns = snd_soc_dai_get_drvdata(dai); + + dev_err(dai->dev, "%s: %s: start\n", __func__, dai->name); dma = snd_soc_dai_get_dma_data(dai, substream); if (!dma) @@ -715,6 +1020,11 @@ static void intel_shutdown(struct snd_pcm_substream *substream, snd_soc_dai_set_dma_data(dai, substream, NULL); kfree(dma); + + pm_runtime_mark_last_busy(cdns->dev); + pm_runtime_put_autosuspend(cdns->dev); + + dev_err(dai->dev, "%s: %s: done\n", __func__, dai->name); } static int intel_pcm_set_sdw_stream(struct snd_soc_dai *dai, @@ -730,14 +1040,20 @@ static int intel_pdm_set_sdw_stream(struct snd_soc_dai *dai, } static const struct snd_soc_dai_ops intel_pcm_dai_ops = { + .startup = intel_startup, .hw_params = intel_hw_params, + .prepare = intel_prepare, + .trigger = intel_trigger, .hw_free = intel_hw_free, .shutdown = intel_shutdown, .set_sdw_stream = intel_pcm_set_sdw_stream, }; static const struct snd_soc_dai_ops intel_pdm_dai_ops = { + .startup = intel_startup, .hw_params = intel_hw_params, + .prepare = intel_prepare, + .trigger = intel_trigger, .hw_free = intel_hw_free, .shutdown = intel_shutdown, .set_sdw_stream = intel_pdm_set_sdw_stream, @@ -866,6 +1182,7 @@ static int sdw_master_read_intel_prop(struct sdw_bus *bus) "intel-sdw-ip-clock", &prop->mclk_freq); + prop->mclk_freq /= 2; fwnode_property_read_u32(link, "intel-quirk-mask", &quirk_mask); @@ -909,24 +1226,24 @@ static int intel_init(struct sdw_intel *sdw) /* * probe and init */ -static int intel_probe(struct platform_device *pdev) +static int intel_master_probe(struct sdw_master_device *md, void *link_ctx) { - struct sdw_cdns_stream_config config; struct sdw_intel *sdw; int ret; - sdw = devm_kzalloc(&pdev->dev, sizeof(*sdw), GFP_KERNEL); + sdw = devm_kzalloc(&md->dev, sizeof(*sdw), GFP_KERNEL); if (!sdw) return -ENOMEM; - sdw->instance = pdev->id; - sdw->res = dev_get_platdata(&pdev->dev); - sdw->cdns.dev = &pdev->dev; - sdw->cdns.registers = sdw->res->registers; - sdw->cdns.instance = sdw->instance; + sdw->instance = md->link_id; + sdw->link_res = link_ctx; + sdw->cdns.dev = &md->dev; + sdw->cdns.registers = sdw->link_res->registers; + sdw->cdns.instance = md->link_id; sdw->cdns.msg_count = 0; - sdw->cdns.bus.dev = &pdev->dev; - sdw->cdns.bus.link_id = pdev->id; + sdw->cdns.bus.dev = &md->dev; + sdw->cdns.bus.link_id = md->link_id; + sdw->link_res->cdns = &sdw->cdns; sdw_cdns_probe(&sdw->cdns); @@ -934,16 +1251,40 @@ static int intel_probe(struct platform_device *pdev) sdw_intel_ops.read_prop = intel_prop_read; sdw->cdns.bus.ops = &sdw_intel_ops; - platform_set_drvdata(pdev, sdw); + md->pdata = sdw; + + /* set driver data, accessed by snd_soc_dai_set_drvdata() */ + dev_set_drvdata(&md->dev, &sdw->cdns); + + /* use generic bandwidth allocation algorithm */ + sdw->cdns.bus.compute_params = sdw_compute_params; ret = sdw_add_bus_master(&sdw->cdns.bus); if (ret) { - dev_err(&pdev->dev, "sdw_add_bus_master fail: %d\n", ret); + dev_err(&md->dev, "sdw_add_bus_master fail: %d\n", ret); return ret; } + if (sdw->cdns.bus.prop.hw_disabled) + dev_info(&md->dev, + "SoundWire master %d is disabled, will be ignored\n", + sdw->cdns.bus.link_id); + + return 0; +} + +static int intel_master_startup(struct sdw_master_device *md) +{ + struct sdw_cdns_stream_config config; + struct sdw_intel *sdw; + int link_flags; + int ret; + + sdw = md->pdata; + if (sdw->cdns.bus.prop.hw_disabled) { - dev_info(&pdev->dev, "SoundWire master %d is disabled, ignoring\n", + dev_info(&md->dev, + "SoundWire master %d is disabled, ignoring\n", sdw->cdns.bus.link_id); return 0; } @@ -961,15 +1302,6 @@ static int intel_probe(struct platform_device *pdev) intel_pdi_ch_update(sdw); - /* Acquire IRQ */ - ret = request_threaded_irq(sdw->res->irq, sdw_cdns_irq, sdw_cdns_thread, - IRQF_SHARED, KBUILD_MODNAME, &sdw->cdns); - if (ret < 0) { - dev_err(sdw->cdns.dev, "unable to grab IRQ %d, disabling device\n", - sdw->res->irq); - goto err_init; - } - ret = sdw_cdns_enable_interrupt(&sdw->cdns, true); if (ret < 0) { dev_err(sdw->cdns.dev, "cannot enable interrupts\n"); @@ -992,43 +1324,276 @@ static int intel_probe(struct platform_device *pdev) intel_debugfs_init(sdw); + /* Enable runtime PM */ + link_flags = md_flags >> (sdw->cdns.bus.link_id * 8); + if (!(link_flags & SDW_INTEL_MASTER_DISABLE_PM_RUNTIME)) { + pm_runtime_set_autosuspend_delay(&md->dev, 3000); + pm_runtime_use_autosuspend(&md->dev); + pm_runtime_mark_last_busy(&md->dev); + + pm_runtime_set_active(&md->dev); + pm_runtime_enable(&md->dev); + } + + /* + * Slave devices have an pm_runtime of 'Unsupported' until + * they report as ATTACHED. If they don't, e.g. because there + * are no Slave devices populated or if the power-on is + * delayed or dependent on a power switch, the Master will + * remain active and prevent its parent from suspending. + * + * Conditionally force the pm_runtime core to re-evaluate the + * Master status in the absence of any Slave activity. A quirk + * is provided to e.g. deal with Slaves that may be powered on + * with a delay. A more complete solution would require the + * definition of Master properties. + */ + if (!(link_flags & SDW_INTEL_MASTER_DISABLE_PM_RUNTIME_IDLE)) + pm_runtime_idle(&md->dev); + return 0; err_interrupt: sdw_cdns_enable_interrupt(&sdw->cdns, false); - free_irq(sdw->res->irq, sdw); err_init: sdw_delete_bus_master(&sdw->cdns.bus); return ret; } -static int intel_remove(struct platform_device *pdev) +static int intel_master_remove(struct sdw_master_device *md) { struct sdw_intel *sdw; - sdw = platform_get_drvdata(pdev); + pm_runtime_disable(&md->dev); + + sdw = md->pdata; if (!sdw->cdns.bus.prop.hw_disabled) { intel_debugfs_exit(sdw); sdw_cdns_enable_interrupt(&sdw->cdns, false); - free_irq(sdw->res->irq, sdw); snd_soc_unregister_component(sdw->cdns.dev); } sdw_delete_bus_master(&sdw->cdns.bus); + md->dev.driver = NULL; + device_unregister(&md->dev); + return 0; } -static struct platform_driver sdw_intel_drv = { - .probe = intel_probe, - .remove = intel_remove, - .driver = { - .name = "int-sdw", +/* + * PM calls + */ - }, +#ifdef CONFIG_PM + +static int intel_suspend(struct device *dev) +{ + struct sdw_master_device *md = to_sdw_master_device(dev); + struct sdw_cdns *cdns = dev_get_drvdata(dev); + struct sdw_intel *sdw = cdns_to_intel(cdns); + int ret; + + if (cdns->bus.prop.hw_disabled) { + dev_dbg(dev, "SoundWire master %d is disabled, ignoring\n", + cdns->bus.link_id); + return 0; + } + + dev_err(dev, "%s start\n", __func__); + + if (pm_runtime_status_suspended(dev)) { + dev_dbg(dev, + "%s: pm_runtime status: suspended\n", + __func__); + + /* + * keep track of the state for the system resume, where + * we will need to reset the pm_runtime status to active + */ + md->pm_runtime_suspended = true; + + return 0; + } + + ret = sdw_cdns_enable_interrupt(cdns, false); + if (ret < 0) { + dev_err(dev, "cannot disable interrupts on suspend\n"); + return ret; + } + + ret = intel_link_power_down(sdw); + if (ret) { + dev_err(dev, "Link power down failed: %d", ret); + return ret; + } + + intel_shim_wake(sdw, false); + + dev_err(dev, "%s done\n", __func__); + + return 0; +} + +static int intel_suspend_runtime(struct device *dev) +{ + struct sdw_cdns *cdns = dev_get_drvdata(dev); + struct sdw_intel *sdw = cdns_to_intel(cdns); + int ret; + + if (cdns->bus.prop.hw_disabled) { + dev_dbg(dev, "SoundWire master %d is disabled, ignoring\n", + cdns->bus.link_id); + return 0; + } + + dev_err(dev, "%s start\n", __func__); + + ret = sdw_cdns_enable_interrupt(cdns, false); + if (ret < 0) { + dev_err(dev, "cannot disable interrupts on suspend\n"); + return ret; + } + + ret = intel_link_power_down(sdw); + if (ret) { + dev_err(dev, "Link power down failed: %d", ret); + return ret; + } + + intel_shim_wake(sdw, false); + + dev_err(dev, "%s done\n", __func__); + + return 0; +} + +static int intel_resume(struct device *dev) +{ + struct sdw_master_device *md = to_sdw_master_device(dev); + struct sdw_cdns *cdns = dev_get_drvdata(dev); + struct sdw_intel *sdw = cdns_to_intel(cdns); + int link_flags; + int ret; + + if (cdns->bus.prop.hw_disabled) { + dev_dbg(dev, "SoundWire master %d is disabled, ignoring\n", + cdns->bus.link_id); + return 0; + } + + dev_err(dev, "%s start\n", __func__); + + if (md->pm_runtime_suspended) { + dev_dbg(dev, + "%s: pm_runtime status was suspended, forcing active\n", + __func__); + + /* follow required sequence from runtime_pm.rst */ + pm_runtime_disable(dev); + pm_runtime_set_active(dev); + pm_runtime_mark_last_busy(dev); + pm_runtime_enable(dev); + + md->pm_runtime_suspended = false; + + link_flags = md_flags >> (sdw->cdns.bus.link_id * 8); + if (!(link_flags & SDW_INTEL_MASTER_DISABLE_PM_RUNTIME_IDLE)) + pm_runtime_idle(&md->dev); + } + + ret = intel_init(sdw); + if (ret) { + dev_err(dev, "%s failed: %d", __func__, ret); + return ret; + } + + /* + * make sure all Slaves are tagged as UNATTACHED and provide + * reason for reinitialization + */ + sdw_clear_slave_status(&sdw->cdns.bus, + SDW_UNATTACH_REQUEST_MASTER_RESET); + + ret = sdw_cdns_enable_interrupt(cdns, true); + if (ret < 0) { + dev_err(dev, "cannot enable interrupts during resume\n"); + return ret; + } + + ret = sdw_cdns_exit_reset(cdns); + if (ret < 0) { + dev_err(dev, "unable to exit bus reset sequence during resume\n"); + return ret; + } + + dev_err(dev, "%s done\n", __func__); + + return ret; +} + +static int intel_resume_runtime(struct device *dev) +{ + struct sdw_cdns *cdns = dev_get_drvdata(dev); + struct sdw_intel *sdw = cdns_to_intel(cdns); + int ret; + + if (cdns->bus.prop.hw_disabled) { + dev_dbg(dev, "SoundWire master %d is disabled, ignoring\n", + cdns->bus.link_id); + return 0; + } + + dev_err(dev, "%s start\n", __func__); + + ret = intel_init(sdw); + if (ret) { + dev_err(dev, "%s failed: %d", __func__, ret); + return ret; + } + + /* + * make sure all Slaves are tagged as UNATTACHED and provide + * reason for reinitialization + */ + sdw_clear_slave_status(&sdw->cdns.bus, + SDW_UNATTACH_REQUEST_MASTER_RESET); + + ret = sdw_cdns_enable_interrupt(cdns, true); + if (ret < 0) { + dev_err(dev, "cannot enable interrupts during resume\n"); + return ret; + } + + ret = sdw_cdns_exit_reset(cdns); + if (ret < 0) { + dev_err(dev, "unable to exit bus reset sequence during resume\n"); + return ret; + } + + dev_err(dev, "%s done\n", __func__); + + return ret; +} + +#endif + +static const struct dev_pm_ops intel_pm = { + SET_SYSTEM_SLEEP_PM_OPS(intel_suspend, intel_resume) + SET_RUNTIME_PM_OPS(intel_suspend_runtime, intel_resume_runtime, NULL) }; -module_platform_driver(sdw_intel_drv); +struct sdw_md_driver intel_sdw_driver = { + .driver = { + .name = "intel-sdw", + .owner = THIS_MODULE, + .pm = &intel_pm, + }, + .probe = intel_master_probe, + .startup = intel_master_startup, + .remove = intel_master_remove, +}; +EXPORT_SYMBOL(intel_sdw_driver); MODULE_LICENSE("Dual BSD/GPL"); MODULE_ALIAS("platform:int-sdw"); diff --git a/drivers/soundwire/intel.h b/drivers/soundwire/intel.h index d923b626233026..2ae4fa7486861d 100644 --- a/drivers/soundwire/intel.h +++ b/drivers/soundwire/intel.h @@ -5,23 +5,32 @@ #define __SDW_INTEL_LOCAL_H /** - * struct sdw_intel_link_res - Soundwire link resources + * struct sdw_intel_link_res - Soundwire Intel link resource structure, + * typically populated by the controller driver. + * @md: master device + * @mmio_base: mmio base of SoundWire registers * @registers: Link IO registers base * @shim: Audio shim pointer * @alh: ALH (Audio Link Hub) pointer * @irq: Interrupt line * @ops: Shim callback ops - * @arg: Shim callback ops argument - * - * This is set as pdata for each link instance. + * @dev: device implementing hw_params and free callbacks */ struct sdw_intel_link_res { + struct sdw_master_device *md; + void __iomem *mmio_base; /* not strictly needed, useful for debug */ void __iomem *registers; void __iomem *shim; void __iomem *alh; int irq; const struct sdw_intel_ops *ops; - void *arg; + struct device *dev; + struct sdw_cdns *cdns; + struct list_head list; }; +#define SDW_INTEL_QUIRK_MASK_BUS_DISABLE BIT(1) + +extern struct sdw_md_driver intel_sdw_driver; + #endif /* __SDW_INTEL_LOCAL_H */ diff --git a/drivers/soundwire/intel_init.c b/drivers/soundwire/intel_init.c index 2a2b4d8df46222..ed2a83989c72ff 100644 --- a/drivers/soundwire/intel_init.c +++ b/drivers/soundwire/intel_init.c @@ -11,8 +11,10 @@ #include #include #include -#include +#include +#include #include +#include "cadence_master.h" #include "intel.h" #define SDW_LINK_TYPE 4 /* from Intel ACPI documentation */ @@ -23,32 +25,47 @@ #define SDW_LINK_BASE 0x30000 #define SDW_LINK_SIZE 0x10000 -static int link_mask; -module_param_named(sdw_link_mask, link_mask, int, 0444); +static int ctrl_link_mask; +module_param_named(sdw_link_mask, ctrl_link_mask, int, 0444); MODULE_PARM_DESC(sdw_link_mask, "Intel link mask (one bit per link)"); -struct sdw_link_data { - struct sdw_intel_link_res res; - struct platform_device *pdev; -}; +static bool is_link_enabled(struct fwnode_handle *fw_node, int i) +{ + struct fwnode_handle *link; + char name[32]; + u32 quirk_mask = 0; -struct sdw_intel_ctx { - int count; - struct sdw_link_data *links; -}; + /* Find master handle */ + snprintf(name, sizeof(name), + "mipi-sdw-link-%d-subproperties", i); + + link = fwnode_get_named_child_node(fw_node, name); + if (!link) + return false; + + fwnode_property_read_u32(link, + "intel-quirk-mask", + &quirk_mask); + + if (quirk_mask & SDW_INTEL_QUIRK_MASK_BUS_DISABLE) + return false; + + return true; +} -static int sdw_intel_cleanup_pdev(struct sdw_intel_ctx *ctx) +static int sdw_intel_cleanup(struct sdw_intel_ctx *ctx) { - struct sdw_link_data *link = ctx->links; + struct sdw_intel_link_res *link = ctx->links; + struct sdw_master_device *md; int i; if (!link) return 0; - for (i = 0; i < ctx->count; i++) { - if (link->pdev) - platform_device_unregister(link->pdev); - link++; + for (i = 0; i < ctx->count; i++, link++) { + md = link->md; + if (md) + md->driver->remove(md); } kfree(ctx->links); @@ -57,115 +74,258 @@ static int sdw_intel_cleanup_pdev(struct sdw_intel_ctx *ctx) return 0; } -static struct sdw_intel_ctx -*sdw_intel_add_controller(struct sdw_intel_res *res) +static int +sdw_intel_scan_controller(struct sdw_intel_acpi_info *info) { - struct platform_device_info pdevinfo; - struct platform_device *pdev; - struct sdw_link_data *link; - struct sdw_intel_ctx *ctx; struct acpi_device *adev; int ret, i; u8 count; - u32 caps; - if (acpi_bus_get_device(res->handle, &adev)) - return NULL; + if (acpi_bus_get_device(info->handle, &adev)) + return -EINVAL; /* Found controller, find links supported */ count = 0; ret = fwnode_property_read_u8_array(acpi_fwnode_handle(adev), "mipi-sdw-master-count", &count, 1); - /* Don't fail on error, continue and use hw value */ + /* + * In theory we could check the number of links supported in + * hardware, but in that step we cannot assume SoundWire IP is + * powered. + * + * In addition, if the BIOS doesn't even provide this + * 'master-count' property then all the inits based on link + * masks will fail as well. + * + * We will check the hardware capabilities in the startup() step + */ + if (ret) { dev_err(&adev->dev, "Failed to read mipi-sdw-master-count: %d\n", ret); - count = SDW_MAX_LINKS; + return -EINVAL; } - /* Check SNDWLCAP.LCOUNT */ - caps = ioread32(res->mmio_base + SDW_SHIM_BASE + SDW_SHIM_LCAP); - caps &= GENMASK(2, 0); - - /* Check HW supported vs property value and use min of two */ - count = min_t(u8, caps, count); - /* Check count is within bounds */ if (count > SDW_MAX_LINKS) { dev_err(&adev->dev, "Link count %d exceeds max %d\n", count, SDW_MAX_LINKS); - return NULL; + return -EINVAL; } else if (!count) { dev_warn(&adev->dev, "No SoundWire links detected\n"); - return NULL; + return -EINVAL; + } + dev_dbg(&adev->dev, "ACPI reports %d SDW Link devices\n", count); + + info->count = count; + + for (i = 0; i < count; i++) { + if (ctrl_link_mask && !(ctrl_link_mask & BIT(i))) { + dev_dbg(&adev->dev, + "Link %d masked, will not be enabled\n", i); + continue; + } + + if (!is_link_enabled(acpi_fwnode_handle(adev), i)) { + dev_dbg(&adev->dev, + "Link %d not selected in firmware\n", i); + continue; + } + + info->link_mask |= BIT(i); } + return 0; +} + +#define HDA_DSP_REG_ADSPIC2 (0x10) +#define HDA_DSP_REG_ADSPIS2 (0x14) +#define HDA_DSP_REG_ADSPIC2_SNDW BIT(5) + +/** + * sdw_intel_enable_irq() - enable/disable Intel SoundWire IRQ + * @mmio_base: The mmio base of the control register + * @enable: true if enable + */ +void sdw_intel_enable_irq(void __iomem *mmio_base, bool enable) +{ + u32 val; + + val = readl(mmio_base + HDA_DSP_REG_ADSPIC2); + + if (enable) + val |= HDA_DSP_REG_ADSPIC2_SNDW; + else + val &= ~HDA_DSP_REG_ADSPIC2_SNDW; + + writel(val, mmio_base + HDA_DSP_REG_ADSPIC2); +} +EXPORT_SYMBOL(sdw_intel_enable_irq); + +static irqreturn_t sdw_intel_irq(int irq, void *dev_id) +{ + struct sdw_intel_ctx *ctx = dev_id; + u32 int_status; + + int_status = readl(ctx->mmio_base + HDA_DSP_REG_ADSPIS2); + if (int_status & HDA_DSP_REG_ADSPIC2_SNDW) { + sdw_intel_enable_irq(ctx->mmio_base, false); + return IRQ_WAKE_THREAD; + } + + return IRQ_NONE; +} + +static irqreturn_t sdw_intel_thread(int irq, void *dev_id) +{ + struct sdw_intel_ctx *ctx = dev_id; + struct sdw_intel_link_res *link; + + list_for_each_entry(link, &ctx->link_list, list) + sdw_cdns_irq(irq, link->cdns); + + sdw_intel_enable_irq(ctx->mmio_base, true); + return IRQ_HANDLED; +} + +static struct sdw_intel_ctx +*sdw_intel_probe_controller(struct sdw_intel_res *res) +{ + struct sdw_intel_link_res *link; + struct sdw_intel_ctx *ctx; + struct acpi_device *adev; + struct sdw_master_device *md; + u32 link_mask; + int count; + int i; + int ret; + + if (!res) + return NULL; + + if (acpi_bus_get_device(res->handle, &adev)) + return NULL; + + if (!res->count) + return NULL; + + count = res->count; dev_dbg(&adev->dev, "Creating %d SDW Link devices\n", count); ctx = kzalloc(sizeof(*ctx), GFP_KERNEL); if (!ctx) return NULL; - ctx->count = count; - ctx->links = kcalloc(ctx->count, sizeof(*ctx->links), GFP_KERNEL); + ctx->links = kcalloc(count, sizeof(*ctx->links), GFP_KERNEL); if (!ctx->links) goto link_err; + ctx->count = count; + ctx->mmio_base = res->mmio_base; + ctx->link_mask = res->link_mask; + ctx->handle = res->handle; + ctx->irq = res->irq; + link = ctx->links; + link_mask = ctx->link_mask; + + INIT_LIST_HEAD(&ctx->link_list); /* Create SDW Master devices */ - for (i = 0; i < count; i++) { - if (link_mask && !(link_mask & BIT(i))) { - dev_dbg(&adev->dev, - "Link %d masked, will not be enabled\n", i); - link++; + for (i = 0; i < count; i++, link++) { + if (link_mask && !(link_mask & BIT(i))) continue; - } - link->res.irq = res->irq; - link->res.registers = res->mmio_base + SDW_LINK_BASE - + (SDW_LINK_SIZE * i); - link->res.shim = res->mmio_base + SDW_SHIM_BASE; - link->res.alh = res->mmio_base + SDW_ALH_BASE; - - link->res.ops = res->ops; - link->res.arg = res->arg; - - memset(&pdevinfo, 0, sizeof(pdevinfo)); - - pdevinfo.parent = res->parent; - pdevinfo.name = "int-sdw"; - pdevinfo.id = i; - pdevinfo.fwnode = acpi_fwnode_handle(adev); - pdevinfo.data = &link->res; - pdevinfo.size_data = sizeof(link->res); - - pdev = platform_device_register_full(&pdevinfo); - if (IS_ERR(pdev)) { - dev_err(&adev->dev, - "platform device creation failed: %ld\n", - PTR_ERR(pdev)); - goto pdev_err; + md = sdw_md_add(&intel_sdw_driver, + res->parent, + acpi_fwnode_handle(adev), + i); + + if (IS_ERR(md)) { + dev_err(&adev->dev, "Could not create link %d\n", i); + goto err; } + link->md = md; + link->mmio_base = res->mmio_base; + link->registers = res->mmio_base + SDW_LINK_BASE + + (SDW_LINK_SIZE * i); + link->shim = res->mmio_base + SDW_SHIM_BASE; + link->alh = res->mmio_base + SDW_ALH_BASE; + link->ops = res->ops; + link->dev = res->dev; + + /* let the SoundWire master driver to its probe */ + md->driver->probe(md, link); + + list_add_tail(&link->list, &ctx->link_list); + } - link->pdev = pdev; - link++; + ret = request_threaded_irq(ctx->irq, + sdw_intel_irq, sdw_intel_thread, + IRQF_SHARED, KBUILD_MODNAME, ctx); + if (ret < 0) { + dev_err(&adev->dev, "unable to grab IRQ %d, disabling device\n", + res->irq); + goto err; } return ctx; -pdev_err: - sdw_intel_cleanup_pdev(ctx); +err: + sdw_intel_cleanup(ctx); link_err: kfree(ctx); return NULL; } +static int +sdw_intel_startup_controller(struct sdw_intel_ctx *ctx) +{ + struct acpi_device *adev; + struct sdw_intel_link_res *link; + struct sdw_master_device *md; + u32 caps; + u32 link_mask; + int i; + + if (acpi_bus_get_device(ctx->handle, &adev)) + return -EINVAL; + + /* Check SNDWLCAP.LCOUNT */ + caps = ioread32(ctx->mmio_base + SDW_SHIM_BASE + SDW_SHIM_LCAP); + caps &= GENMASK(2, 0); + + /* Check HW supported vs property value */ + if (caps < ctx->count) { + dev_err(&adev->dev, + "BIOS master count is larger than hardware capabilities\n"); + return -EINVAL; + } + + if (!ctx->links) + return -EINVAL; + + link = ctx->links; + link_mask = ctx->link_mask; + + /* Create SDW Master devices */ + for (i = 0; i < ctx->count; i++, link++) { + if (link_mask && !(link_mask & BIT(i))) + continue; + + md = link->md; + + md->driver->startup(md); + } + + return 0; +} + static acpi_status sdw_intel_acpi_cb(acpi_handle handle, u32 level, void *cdata, void **return_value) { - struct sdw_intel_res *res = cdata; + struct sdw_intel_acpi_info *info = cdata; struct acpi_device *adev; acpi_status status; u64 adr; @@ -179,7 +339,7 @@ static acpi_status sdw_intel_acpi_cb(acpi_handle handle, u32 level, return AE_NOT_FOUND; } - res->handle = handle; + info->handle = handle; /* * On some Intel platforms, multiple children of the HDAS @@ -196,39 +356,67 @@ static acpi_status sdw_intel_acpi_cb(acpi_handle handle, u32 level, } /** - * sdw_intel_init() - SoundWire Intel init routine + * sdw_intel_acpi_scan() - SoundWire Intel init routine * @parent_handle: ACPI parent handle - * @res: resource data + * @info: description of what firmware/DSDT tables expose * - * This scans the namespace and creates SoundWire link controller devices - * based on the info queried. + * This scans the namespace and queries firmware to figure out which + * links to enable. A follow-up use of sdw_intel_probe() and + * sdw_intel_startup() is required for creation of devices and bus + * startup */ -void *sdw_intel_init(acpi_handle *parent_handle, struct sdw_intel_res *res) +int sdw_intel_acpi_scan(acpi_handle *parent_handle, + struct sdw_intel_acpi_info *info) { acpi_status status; status = acpi_walk_namespace(ACPI_TYPE_DEVICE, parent_handle, 1, sdw_intel_acpi_cb, - NULL, res, NULL); + NULL, info, NULL); if (ACPI_FAILURE(status)) - return NULL; + return -ENODEV; - return sdw_intel_add_controller(res); + return sdw_intel_scan_controller(info); } -EXPORT_SYMBOL(sdw_intel_init); +EXPORT_SYMBOL(sdw_intel_acpi_scan); +/** + * sdw_intel_probe() - SoundWire Intel probe routine + * @parent_handle: ACPI parent handle + * @res: resource data + * + * This creates SoundWire Master and Slave devices below the controller. + * All the information necessary is stored in the context, and the res + * argument pointer can be freed after this step. + */ +struct sdw_intel_ctx +*sdw_intel_probe(struct sdw_intel_res *res) +{ + return sdw_intel_probe_controller(res); +} +EXPORT_SYMBOL(sdw_intel_probe); + +/** + * sdw_intel_startup() - SoundWire Intel startup + * @ctx: SoundWire context allocated in the probe + * + */ +int sdw_intel_startup(struct sdw_intel_ctx *ctx) +{ + return sdw_intel_startup_controller(ctx); +} +EXPORT_SYMBOL(sdw_intel_startup); /** * sdw_intel_exit() - SoundWire Intel exit - * @arg: callback context + * @ctx: SoundWire context allocated in the probe * * Delete the controller instances created and cleanup */ -void sdw_intel_exit(void *arg) +void sdw_intel_exit(struct sdw_intel_ctx *ctx) { - struct sdw_intel_ctx *ctx = arg; - - sdw_intel_cleanup_pdev(ctx); + free_irq(ctx->irq, ctx); + sdw_intel_cleanup(ctx); kfree(ctx); } EXPORT_SYMBOL(sdw_intel_exit); diff --git a/drivers/soundwire/master.c b/drivers/soundwire/master.c new file mode 100644 index 00000000000000..d23111d43fd6f3 --- /dev/null +++ b/drivers/soundwire/master.c @@ -0,0 +1,64 @@ +// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause) +// Copyright(c) 2019 Intel Corporation. + +#include +#include +#include +#include +#include "bus.h" + +static void sdw_md_release(struct device *dev) +{ + struct sdw_master_device *md = to_sdw_master_device(dev); + + kfree(md); +} + +struct device_type sdw_md_type = { + .name = "soundwire_master", + .release = sdw_md_release, +}; + +struct sdw_master_device *sdw_md_add(struct sdw_md_driver *driver, + struct device *parent, + struct fwnode_handle *fwnode, + int link_id) +{ + struct sdw_master_device *md; + int ret; + + if (!driver->probe) { + dev_err(parent, "mandatory probe callback missing\n"); + return ERR_PTR(-EINVAL); + } + + md = kzalloc(sizeof(*md), GFP_KERNEL); + if (!md) + return ERR_PTR(-ENOMEM); + + md->link_id = link_id; + + md->driver = driver; + + md->dev.parent = parent; + md->dev.fwnode = fwnode; + md->dev.bus = &sdw_bus_type; + md->dev.type = &sdw_md_type; + md->dev.dma_mask = md->dev.parent->dma_mask; + dev_set_name(&md->dev, "sdw-master-%d", md->link_id); + + ret = device_register(&md->dev); + if (ret) { + dev_err(parent, "Failed to add master: ret %d\n", ret); + /* + * On err, don't free but drop ref as this will be freed + * when release method is invoked. + */ + put_device(&md->dev); + } + + md->dev.driver = &driver->driver; + + return md; +} +EXPORT_SYMBOL(sdw_md_add); diff --git a/drivers/soundwire/slave.c b/drivers/soundwire/slave.c index 6473fa602f82cc..543f4ddd9de0fb 100644 --- a/drivers/soundwire/slave.c +++ b/drivers/soundwire/slave.c @@ -9,11 +9,16 @@ static void sdw_slave_release(struct device *dev) { - struct sdw_slave *slave = dev_to_sdw_dev(dev); + struct sdw_slave *slave = to_sdw_slave_device(dev); kfree(slave); } +struct device_type sdw_slave_type = { + .name = "sdw_slave", + .release = sdw_slave_release, +}; + static int sdw_slave_add(struct sdw_bus *bus, struct sdw_slave_id *id, struct fwnode_handle *fwnode) { @@ -29,17 +34,28 @@ static int sdw_slave_add(struct sdw_bus *bus, slave->dev.parent = bus->dev; slave->dev.fwnode = fwnode; - /* name shall be sdw:link:mfg:part:class:unique */ - dev_set_name(&slave->dev, "sdw:%x:%x:%x:%x:%x", - bus->link_id, id->mfg_id, id->part_id, - id->class_id, id->unique_id); + if (id->unique_id == SDW_IGNORED_UNIQUE_ID) { + /* name shall be sdw:link:mfg:part:class */ + dev_set_name(&slave->dev, "sdw:%x:%x:%x:%x", + bus->link_id, id->mfg_id, id->part_id, + id->class_id); + } else { + /* name shall be sdw:link:mfg:part:class:unique */ + dev_set_name(&slave->dev, "sdw:%x:%x:%x:%x:%x", + bus->link_id, id->mfg_id, id->part_id, + id->class_id, id->unique_id); + } - slave->dev.release = sdw_slave_release; slave->dev.bus = &sdw_bus_type; slave->dev.of_node = of_node_get(to_of_node(fwnode)); + slave->dev.type = &sdw_slave_type; slave->bus = bus; slave->status = SDW_SLAVE_UNATTACHED; + init_completion(&slave->enumeration_complete); + init_completion(&slave->initialization_complete); slave->dev_num = 0; + init_completion(&slave->probe_complete); + slave->probed = false; mutex_lock(&bus->bus_lock); list_add_tail(&slave->node, &bus->slaves); @@ -64,6 +80,36 @@ static int sdw_slave_add(struct sdw_bus *bus, } #if IS_ENABLED(CONFIG_ACPI) + +static bool find_slave(struct sdw_bus *bus, + struct acpi_device *adev, + struct sdw_slave_id *id) +{ + unsigned long long addr; + unsigned int link_id; + acpi_status status; + + status = acpi_evaluate_integer(adev->handle, + METHOD_NAME__ADR, NULL, &addr); + + if (ACPI_FAILURE(status)) { + dev_err(bus->dev, "_ADR resolution failed: %x\n", + status); + return false; + } + + /* Extract link id from ADR, Bit 51 to 48 (included) */ + link_id = (addr >> 48) & GENMASK(3, 0); + + /* Check for link_id match */ + if (link_id != bus->link_id) + return false; + + sdw_extract_slave_id(bus, addr, id); + + return true; +} + /* * sdw_acpi_find_slaves() - Find Slave devices in Master ACPI node * @bus: SDW bus instance @@ -73,6 +119,7 @@ static int sdw_slave_add(struct sdw_bus *bus, int sdw_acpi_find_slaves(struct sdw_bus *bus) { struct acpi_device *adev, *parent; + struct acpi_device *adev2, *parent2; parent = ACPI_COMPANION(bus->dev); if (!parent) { @@ -81,28 +128,46 @@ int sdw_acpi_find_slaves(struct sdw_bus *bus) } list_for_each_entry(adev, &parent->children, node) { - unsigned long long addr; struct sdw_slave_id id; - unsigned int link_id; - acpi_status status; + struct sdw_slave_id id2; + bool ignore_unique_id = true; - status = acpi_evaluate_integer(adev->handle, - METHOD_NAME__ADR, NULL, &addr); + if (!find_slave(bus, adev, &id)) + continue; - if (ACPI_FAILURE(status)) { - dev_err(bus->dev, "_ADR resolution failed: %x\n", - status); - return status; + /* brute-force O(N^2) search for duplicates */ + parent2 = parent; + list_for_each_entry(adev2, &parent2->children, node) { + + if (adev == adev2) + continue; + + if (!find_slave(bus, adev2, &id2)) + continue; + + if (id.sdw_version != id2.sdw_version || + id.mfg_id != id2.mfg_id || + id.part_id != id2.part_id || + id.class_id != id2.class_id) + continue; + + if (id.unique_id != id2.unique_id) { + dev_dbg(bus->dev, + "Valid unique IDs %x %x for Slave mfg %x part %d\n", + id.unique_id, id2.unique_id, + id.mfg_id, id.part_id); + ignore_unique_id = false; + } else { + dev_err(bus->dev, + "Invalid unique IDs %x %x for Slave mfg %x part %d\n", + id.unique_id, id2.unique_id, + id.mfg_id, id.part_id); + return -ENODEV; + } } - /* Extract link id from ADR, Bit 51 to 48 (included) */ - link_id = (addr >> 48) & GENMASK(3, 0); - - /* Check for link_id match */ - if (link_id != bus->link_id) - continue; - - sdw_extract_slave_id(bus, addr, &id); + if (ignore_unique_id) + id.unique_id = SDW_IGNORED_UNIQUE_ID; /* * don't error check for sdw_slave_add as we want to continue diff --git a/drivers/soundwire/stream.c b/drivers/soundwire/stream.c index e69f94a8c3a865..f2054fcc6821e2 100644 --- a/drivers/soundwire/stream.c +++ b/drivers/soundwire/stream.c @@ -26,6 +26,8 @@ int sdw_rows[SDW_FRAME_ROWS] = {48, 50, 60, 64, 75, 80, 125, 147, 192, 200, 240, 256, 72, 144, 90, 180}; int sdw_cols[SDW_FRAME_COLS] = {2, 4, 6, 8, 10, 12, 14, 16}; +EXPORT_SYMBOL(sdw_rows); +EXPORT_SYMBOL(sdw_cols); int sdw_find_col_index(int col) { @@ -1460,7 +1462,8 @@ static void sdw_release_bus_lock(struct sdw_stream_runtime *stream) } } -static int _sdw_prepare_stream(struct sdw_stream_runtime *stream) +static int _sdw_prepare_stream(struct sdw_stream_runtime *stream, + bool update_params) { struct sdw_master_runtime *m_rt; struct sdw_bus *bus = NULL; @@ -1480,6 +1483,9 @@ static int _sdw_prepare_stream(struct sdw_stream_runtime *stream) return -EINVAL; } + if (!update_params) + goto program_params; + /* Increment cumulative bus bandwidth */ /* TODO: Update this during Device-Device support */ bus->params.bandwidth += m_rt->stream->params.rate * @@ -1495,6 +1501,7 @@ static int _sdw_prepare_stream(struct sdw_stream_runtime *stream) } } +program_params: /* Program params */ ret = sdw_program_params(bus); if (ret < 0) { @@ -1544,7 +1551,10 @@ static int _sdw_prepare_stream(struct sdw_stream_runtime *stream) */ int sdw_prepare_stream(struct sdw_stream_runtime *stream) { - int ret = 0; + bool update_params = true; + int ret; + + pr_err("%s: %s start\n", __func__, stream->name); if (!stream) { pr_err("SoundWire: Handle not found for stream\n"); @@ -1553,11 +1563,39 @@ int sdw_prepare_stream(struct sdw_stream_runtime *stream) sdw_acquire_bus_lock(stream); - ret = _sdw_prepare_stream(stream); + if (stream->state == SDW_STREAM_PREPARED) { + ret = 0; + goto state_err; + } + + if (stream->state != SDW_STREAM_CONFIGURED && + stream->state != SDW_STREAM_DEPREPARED && + stream->state != SDW_STREAM_DISABLED) { + pr_err("%s: %s: inconsistent state state %d\n", + __func__, stream->name, stream->state); + ret = -EINVAL; + goto state_err; + } + + /* + * when the stream is DISABLED, this means sdw_prepare_stream() + * is called as a result of an underflow or a resume operation. + * In this case, the bus parameters shall not be recomputed, but + * still need to be re-applied + */ + if (stream->state == SDW_STREAM_DISABLED) + update_params = false; + + ret = _sdw_prepare_stream(stream, update_params); + if (ret < 0) pr_err("Prepare for stream:%s failed: %d\n", stream->name, ret); +state_err: sdw_release_bus_lock(stream); + + pr_err("%s: %s: done\n", __func__, stream->name); + return ret; } EXPORT_SYMBOL(sdw_prepare_stream); @@ -1614,6 +1652,8 @@ int sdw_enable_stream(struct sdw_stream_runtime *stream) { int ret; + pr_err("%s: %s: start\n", __func__, stream->name); + if (!stream) { pr_err("SoundWire: Handle not found for stream\n"); return -EINVAL; @@ -1621,11 +1661,22 @@ int sdw_enable_stream(struct sdw_stream_runtime *stream) sdw_acquire_bus_lock(stream); + if (stream->state != SDW_STREAM_PREPARED && + stream->state != SDW_STREAM_DISABLED) { + pr_err("%s: %s: inconsistent state state %d\n", + __func__, stream->name, stream->state); + ret = -EINVAL; + goto state_err; + } + ret = _sdw_enable_stream(stream); if (ret < 0) pr_err("Enable for stream:%s failed: %d\n", stream->name, ret); +state_err: sdw_release_bus_lock(stream); + + pr_err("%s: %s: done\n", __func__, stream->name); return ret; } EXPORT_SYMBOL(sdw_enable_stream); @@ -1690,6 +1741,8 @@ int sdw_disable_stream(struct sdw_stream_runtime *stream) { int ret; + pr_err("%s: %s: start\n", __func__, stream->name); + if (!stream) { pr_err("SoundWire: Handle not found for stream\n"); return -EINVAL; @@ -1697,11 +1750,22 @@ int sdw_disable_stream(struct sdw_stream_runtime *stream) sdw_acquire_bus_lock(stream); + if (stream->state != SDW_STREAM_ENABLED) { + pr_err("%s: %s: inconsistent state state %d\n", + __func__, stream->name, stream->state); + ret = -EINVAL; + goto state_err; + } + ret = _sdw_disable_stream(stream); if (ret < 0) pr_err("Disable for stream:%s failed: %d\n", stream->name, ret); +state_err: sdw_release_bus_lock(stream); + + pr_err("%s: %s: done\n", __func__, stream->name); + return ret; } EXPORT_SYMBOL(sdw_disable_stream); @@ -1726,6 +1790,16 @@ static int _sdw_deprepare_stream(struct sdw_stream_runtime *stream) bus->params.bandwidth -= m_rt->stream->params.rate * m_rt->ch_count * m_rt->stream->params.bps; + /* Compute params */ + if (bus->compute_params) { + ret = bus->compute_params(bus); + if (ret < 0) { + dev_err(bus->dev, "Compute params failed: %d", + ret); + return ret; + } + } + /* Program params */ ret = sdw_program_params(bus); if (ret < 0) { @@ -1749,17 +1823,32 @@ int sdw_deprepare_stream(struct sdw_stream_runtime *stream) { int ret; + pr_err("%s: %s: start\n", __func__, stream->name); + if (!stream) { pr_err("SoundWire: Handle not found for stream\n"); return -EINVAL; } sdw_acquire_bus_lock(stream); + + if (stream->state != SDW_STREAM_PREPARED && + stream->state != SDW_STREAM_DISABLED) { + pr_err("%s: %s: inconsistent state state %d\n", + __func__, stream->name, stream->state); + ret = -EINVAL; + goto state_err; + } + ret = _sdw_deprepare_stream(stream); if (ret < 0) pr_err("De-prepare for stream:%d failed: %d\n", ret, ret); +state_err: sdw_release_bus_lock(stream); + + pr_err("%s: %s: done\n", __func__, stream->name); + return ret; } EXPORT_SYMBOL(sdw_deprepare_stream); diff --git a/include/linux/soundwire/sdw.h b/include/linux/soundwire/sdw.h index 688b40e65c8988..db3d780cdf0f77 100644 --- a/include/linux/soundwire/sdw.h +++ b/include/linux/soundwire/sdw.h @@ -403,6 +403,8 @@ int sdw_slave_read_prop(struct sdw_slave *slave); * SDW Slave Structures and APIs */ +#define SDW_IGNORED_UNIQUE_ID 0xFF + /** * struct sdw_slave_id - Slave ID * @mfg_id: MIPI Manufacturer ID @@ -418,7 +420,7 @@ struct sdw_slave_id { __u16 mfg_id; __u16 part_id; __u8 class_id; - __u8 unique_id:4; + __u8 unique_id; __u8 sdw_version:4; }; @@ -544,7 +546,20 @@ struct sdw_slave_ops { * @debugfs: Slave debugfs * @node: node for bus list * @port_ready: Port ready completion flag for each Slave port - * @dev_num: Device Number assigned by Bus + * @dev_num: Current Device Number, values can be 0 or dev_num_sticky + * @dev_num_sticky: one-time static Device Number assigned by Bus + * @probed: boolean tracking driver state + * @probe_complete: completion utility to control potential races + * on startup between driver probe/initialization and SoundWire + * Slave state changes/imp-def interrupts + * @enumeration_complete: completion utility to control potential races + * on startup between device enumeration and read/write access to the + * Slave device + * @initialization_complete: completion utility to control potential races + * on startup between device enumeration and settings being restored + * @unattach_request: status field to keep track why the Slave was + * re-initialized. This is useful to deal with potential race conditions + * between the master suspending and the codec resuming. */ struct sdw_slave { struct sdw_slave_id id; @@ -559,9 +574,27 @@ struct sdw_slave { struct list_head node; struct completion *port_ready; u16 dev_num; + u16 dev_num_sticky; + bool probed; + struct completion probe_complete; + struct completion enumeration_complete; + struct completion initialization_complete; + int unattach_request; +}; + +#define to_sdw_slave_device(d) \ + container_of(d, struct sdw_slave, dev) + +struct sdw_master_device { + struct device dev; + int link_id; + struct sdw_md_driver *driver; + bool pm_runtime_suspended; + void *pdata; /* core does not touch */ }; -#define dev_to_sdw_dev(_dev) container_of(_dev, struct sdw_slave, dev) +#define to_sdw_master_device(d) \ + container_of(d, struct sdw_master_device, dev) struct sdw_driver { const char *name; @@ -577,6 +610,27 @@ struct sdw_driver { struct device_driver driver; }; +struct sdw_md_driver { + /* initializations and allocations */ + int (*probe)(struct sdw_master_device *md, void *link_ctx); + /* hardware enablement, all clock/power dependencies are available */ + int (*startup)(struct sdw_master_device *md); + /* hardware disabled */ + int (*shutdown)(struct sdw_master_device *md); + /* free all resources */ + int (*remove)(struct sdw_master_device *md); + /* + * enable/disable driver control while in clock-stop mode, + * typically in always-on/D0ix modes. When the driver yields + * control, another entity in the system (typically firmware + * running on an always-on microprocessor) is responsible to + * tracking Slave-initiated wakes + */ + int (*autonomous_clock_stop_enable)(struct sdw_master_device *md, + bool state); + struct device_driver driver; +}; + #define SDW_SLAVE_ENTRY(_mfg_id, _part_id, _drv_data) \ { .mfg_id = (_mfg_id), .part_id = (_part_id), \ .driver_data = (unsigned long)(_drv_data) } @@ -766,6 +820,11 @@ struct sdw_bus { int sdw_add_bus_master(struct sdw_bus *bus); void sdw_delete_bus_master(struct sdw_bus *bus); +struct sdw_master_device *sdw_md_add(struct sdw_md_driver *driver, + struct device *parent, + struct fwnode_handle *fwnode, + int link_id); + /** * sdw_port_config: Master or Slave Port configuration * @@ -851,6 +910,9 @@ struct sdw_stream_runtime { struct sdw_stream_runtime *sdw_alloc_stream(const char *stream_name); void sdw_release_stream(struct sdw_stream_runtime *stream); + +int sdw_compute_params(struct sdw_bus *bus); + int sdw_stream_add_master(struct sdw_bus *bus, struct sdw_stream_config *stream_config, struct sdw_port_config *port_config, diff --git a/include/linux/soundwire/sdw_intel.h b/include/linux/soundwire/sdw_intel.h index c9427cb6020b3e..373947003cea01 100644 --- a/include/linux/soundwire/sdw_intel.h +++ b/include/linux/soundwire/sdw_intel.h @@ -4,36 +4,123 @@ #ifndef __SDW_INTEL_H #define __SDW_INTEL_H +/** + * struct sdw_intel_stream_params_data: configuration passed during + * the @params_stream callback, e.g. for interaction with DSP + * firmware. + */ +struct sdw_intel_stream_params_data { + struct snd_pcm_substream *substream; + struct snd_soc_dai *dai; + struct snd_pcm_hw_params *hw_params; + int link_id; + int alh_stream_id; +}; + +/** + * struct sdw_intel_stream_free_data: configuration passed during + * the @free_stream callback, e.g. for interaction with DSP + * firmware. + */ +struct sdw_intel_stream_free_data { + struct snd_pcm_substream *substream; + struct snd_soc_dai *dai; + int link_id; +}; + /** * struct sdw_intel_ops: Intel audio driver callback ops * - * @config_stream: configure the stream with the hw_params - * the first argument containing the context is mandatory */ struct sdw_intel_ops { - int (*config_stream)(void *arg, void *substream, - void *dai, void *hw_params, int stream_num); + int (*params_stream)(struct device *dev, + struct sdw_intel_stream_params_data *params_data); + int (*free_stream)(struct device *dev, + struct sdw_intel_stream_free_data *free_data); +}; + +/** + * struct sdw_intel_acpi_info - Soundwire Intel information found in ACPI tables + * @handle: ACPI controller handle + * @count: link count found with "sdw-master-count" property + * @link_mask: bit-wise mask listing links enabled by BIOS menu + * + * this structure could be expanded to e.g. provide all the _ADR + * information in case the link_mask is not sufficient to identify + * platform capabilities. + */ +struct sdw_intel_acpi_info { + acpi_handle handle; + int count; + u32 link_mask; }; /** - * struct sdw_intel_res - Soundwire Intel resource structure + * struct sdw_intel_res - Soundwire Intel global resource structure, + * typically populated by the DSP driver + * + * @count: link count (may be filtered by DSP driver) * @mmio_base: mmio base of SoundWire registers * @irq: interrupt number * @handle: ACPI parent handle * @parent: parent device * @ops: callback ops - * @arg: callback arg + * @dev: device implementing hwparams and free callbacks + * @link_mask: bit-wise mask listing links selected by the DSP driver */ struct sdw_intel_res { + int count; void __iomem *mmio_base; int irq; acpi_handle handle; struct device *parent; const struct sdw_intel_ops *ops; - void *arg; + struct device *dev; + u32 link_mask; +}; + +struct sdw_intel_link_res; + +/** + * struct sdw_intel_ctx - context allocated by the controller + * driver probe + * @count: link count + * @mmio_base: mmio base of SoundWire registers, only used to check + * hardware capabilities after all power dependencies are settled. + * @arg: Shim callback ops argument + */ +struct sdw_intel_ctx { + int count; + void __iomem *mmio_base; + u32 link_mask; + acpi_handle handle; + int irq; + struct sdw_intel_link_res *links; + struct list_head link_list; }; -void *sdw_intel_init(acpi_handle *parent_handle, struct sdw_intel_res *res); -void sdw_intel_exit(void *arg); +/* + * On Intel platforms, the SoundWire IP has dependencies on power + * rails shared with the DSP, and the initialization steps are split + * in three. First an ACPI scan to check what the firmware describes + * in DSDT tables, then an allocation step (with no hardware + * configuration but with all the relevant devices created) and last + * the actual hardware configuration. The final stage is a global + * interrupt enable which is controlled by the DSP driver. Splitting + * these phases helps simplify the boot flow and make early decisions + * on e.g. which machine driver to select (I2S mode, HDaudio or + * SoundWire). + */ +int sdw_intel_acpi_scan(acpi_handle *parent_handle, + struct sdw_intel_acpi_info *info); + +struct sdw_intel_ctx * +sdw_intel_probe(struct sdw_intel_res *res); + +int sdw_intel_startup(struct sdw_intel_ctx *ctx); + +void sdw_intel_exit(struct sdw_intel_ctx *ctx); + +void sdw_intel_enable_irq(void __iomem *mmio_base, bool enable); #endif diff --git a/include/linux/soundwire/sdw_type.h b/include/linux/soundwire/sdw_type.h index aaa7f4267c149a..463d6d018d568d 100644 --- a/include/linux/soundwire/sdw_type.h +++ b/include/linux/soundwire/sdw_type.h @@ -5,14 +5,31 @@ #define __SOUNDWIRE_TYPES_H extern struct bus_type sdw_bus_type; +extern struct device_type sdw_slave_type; +extern struct device_type sdw_md_type; -#define drv_to_sdw_driver(_drv) container_of(_drv, struct sdw_driver, driver) +static inline int is_sdw_slave(const struct device *dev) +{ + return dev->type == &sdw_slave_type; +} -#define sdw_register_driver(drv) \ - __sdw_register_driver(drv, THIS_MODULE) +static inline int is_sdw_md(const struct device *dev) +{ + return dev->type == &sdw_md_type; +} -int __sdw_register_driver(struct sdw_driver *drv, struct module *owner); -void sdw_unregister_driver(struct sdw_driver *drv); +#define to_sdw_slave_driver(_drv) \ + container_of(_drv, struct sdw_driver, driver) + +#define to_sdw_md_driver(_drv) \ + container_of(_drv, struct sdw_md_driver, driver) + +#define sdw_register_slave_driver(drv) \ + __sdw_register_slave_driver(drv, THIS_MODULE) + +int __sdw_register_slave_driver(struct sdw_driver *drv, + struct module *owner); +void sdw_unregister_slave_driver(struct sdw_driver *drv); int sdw_slave_modalias(const struct sdw_slave *slave, char *buf, size_t size); @@ -24,7 +41,8 @@ int sdw_slave_modalias(const struct sdw_slave *slave, char *buf, size_t size); * module init/exit. This eliminates a lot of boilerplate. Each module may only * use this macro once, and calling it replaces module_init() and module_exit() */ -#define module_sdw_driver(__sdw_driver) \ - module_driver(__sdw_driver, sdw_register_driver, \ - sdw_unregister_driver) +#define module_sdw_driver(__sdw_slave_driver) \ + module_driver(__sdw_slave_driver, sdw_register_slave_driver, \ + sdw_unregister_slave_driver) + #endif /* __SOUNDWIRE_TYPES_H */ diff --git a/include/sound/soc-acpi-intel-match.h b/include/sound/soc-acpi-intel-match.h index 20c0bee3b959db..ab6f75a86611dc 100644 --- a/include/sound/soc-acpi-intel-match.h +++ b/include/sound/soc-acpi-intel-match.h @@ -31,6 +31,12 @@ extern struct snd_soc_acpi_mach snd_soc_acpi_intel_tgl_machines[]; extern struct snd_soc_acpi_mach snd_soc_acpi_intel_ehl_machines[]; extern struct snd_soc_acpi_mach snd_soc_acpi_intel_jsl_machines[]; +extern struct snd_soc_acpi_mach snd_soc_acpi_intel_cnl_sdw_machines[]; +extern struct snd_soc_acpi_mach snd_soc_acpi_intel_cfl_sdw_machines[]; +extern struct snd_soc_acpi_mach snd_soc_acpi_intel_cml_sdw_machines[]; +extern struct snd_soc_acpi_mach snd_soc_acpi_intel_icl_sdw_machines[]; +extern struct snd_soc_acpi_mach snd_soc_acpi_intel_tgl_sdw_machines[]; + /* * generic table used for HDA codec-based platforms, possibly with * additional ACPI-enumerated codecs diff --git a/include/sound/sof/dai-intel.h b/include/sound/sof/dai-intel.h index 5f1ef5565be69f..04e48227f542b5 100644 --- a/include/sound/sof/dai-intel.h +++ b/include/sound/sof/dai-intel.h @@ -87,6 +87,15 @@ struct sof_ipc_dai_hda_params { uint32_t link_dma_ch; } __packed; +/* ALH Configuration Request - SOF_IPC_DAI_ALH_CONFIG */ +struct sof_ipc_dai_alh_params { + struct sof_ipc_hdr hdr; + uint32_t stream_id; + + /* reserved for future use */ + uint32_t reserved[15]; +} __packed; + /* DMIC Configuration Request - SOF_IPC_DAI_DMIC_CONFIG */ /* This struct is defined per 2ch PDM controller available in the platform. @@ -179,13 +188,4 @@ struct sof_ipc_dai_dmic_params { struct sof_ipc_dai_dmic_pdm_ctrl pdm[0]; } __packed; -/* ALH Configuration Request - SOF_IPC_DAI_ALH_CONFIG */ -struct sof_ipc_dai_alh_params { - struct sof_ipc_hdr hdr; - uint32_t stream_id; - - /* reserved for future use */ - uint32_t reserved[15]; -} __packed; - #endif diff --git a/sound/hda/intel-dsp-config.c b/sound/hda/intel-dsp-config.c index be1df80ed01337..6e6e8662534d8a 100644 --- a/sound/hda/intel-dsp-config.c +++ b/sound/hda/intel-dsp-config.c @@ -204,6 +204,12 @@ static const struct config_entry config_table[] = { DMI_MATCH(DMI_SYS_VENDOR, "Google"), } }, + { + .ident = "Dell laptop", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc"), + } + }, {} } }, diff --git a/sound/soc/Makefile b/sound/soc/Makefile index 861a21b7948443..429add4afe3827 100644 --- a/sound/soc/Makefile +++ b/sound/soc/Makefile @@ -1,3 +1,5 @@ +ccflags-y += -DDEBUG + # SPDX-License-Identifier: GPL-2.0 snd-soc-core-objs := soc-core.o soc-dapm.o soc-jack.o soc-utils.o soc-dai.o soc-component.o snd-soc-core-objs += soc-pcm.o soc-io.o soc-devres.o soc-ops.o diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index 925e5f852348a8..036dc714ac1dc8 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -1056,6 +1056,39 @@ config SND_SOC_RT5677_SPI config SND_SOC_RT5682 tristate +config SND_SOC_RT700 + tristate + +config SND_SOC_RT700_SDW + tristate "Realtek RT700 Codec - SDW" + depends on SOUNDWIRE + select SND_SOC_RT700 + select REGMAP_SOUNDWIRE + +config SND_SOC_RT1308_SDW + tristate "Realtek RT1308 Codec - SDW" + depends on SOUNDWIRE + select SND_SOC_RT1308 + select REGMAP_SOUNDWIRE + +config SND_SOC_RT711 + tristate + +config SND_SOC_RT711_SDW + tristate "Realtek RT711 Codec - SDW" + depends on SOUNDWIRE + select SND_SOC_RT711 + select REGMAP_SOUNDWIRE + +config SND_SOC_RT715 + tristate + +config SND_SOC_RT715_SDW + tristate "Realtek RT715 Codec - SDW" + depends on SOUNDWIRE + select SND_SOC_RT715 + select REGMAP_SOUNDWIRE + #Freescale sgtl5000 codec config SND_SOC_SGTL5000 tristate "Freescale SGTL5000 CODEC" diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index ddfd07071925c2..823de4b00f1640 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -1,3 +1,5 @@ +ccflags-y += -DDEBUG + # SPDX-License-Identifier: GPL-2.0 snd-soc-88pm860x-objs := 88pm860x-codec.o snd-soc-ab8500-codec-objs := ab8500-codec.o @@ -278,6 +280,11 @@ snd-soc-wm9712-objs := wm9712.o snd-soc-wm9713-objs := wm9713.o snd-soc-wm-hubs-objs := wm_hubs.o snd-soc-zx-aud96p22-objs := zx_aud96p22.o +snd-soc-rt700-objs := rt700.o rt700-sdw.o +snd-soc-rt1308-sdw-objs := rt1308-sdw.o +snd-soc-rt711-objs := rt711.o rt711-sdw.o +snd-soc-rt715-objs := rt715.o rt715-sdw.o + # Amp snd-soc-max9877-objs := max9877.o snd-soc-max98504-objs := max98504.o @@ -567,6 +574,10 @@ obj-$(CONFIG_SND_SOC_WM9713) += snd-soc-wm9713.o obj-$(CONFIG_SND_SOC_WM_ADSP) += snd-soc-wm-adsp.o obj-$(CONFIG_SND_SOC_WM_HUBS) += snd-soc-wm-hubs.o obj-$(CONFIG_SND_SOC_ZX_AUD96P22) += snd-soc-zx-aud96p22.o +obj-$(CONFIG_SND_SOC_RT700) += snd-soc-rt700.o +obj-$(CONFIG_SND_SOC_RT1308_SDW) += snd-soc-rt1308-sdw.o +obj-$(CONFIG_SND_SOC_RT711) += snd-soc-rt711.o +obj-$(CONFIG_SND_SOC_RT715) += snd-soc-rt715.o # Amp obj-$(CONFIG_SND_SOC_MAX9877) += snd-soc-max9877.o diff --git a/sound/soc/codecs/rt1308-sdw.c b/sound/soc/codecs/rt1308-sdw.c new file mode 100644 index 00000000000000..c60db67e33eb22 --- /dev/null +++ b/sound/soc/codecs/rt1308-sdw.c @@ -0,0 +1,738 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// rt1308-sdw.c -- rt1308 ALSA SoC audio driver +// +// Copyright(c) 2019 Realtek Semiconductor Corp. +// +// +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "rt1308.h" +#include "rt1308-sdw.h" + +static bool rt1308_readable_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case 0x00e0: + case 0x00f0: + case 0x2f01 ... 0x2f07: + case 0x3000 ... 0x3001: + case 0x3004 ... 0x3005: + case 0x3008: + case 0x300a: + case 0xc000 ... 0xcff3: + return true; + default: + return false; + } +} + +static bool rt1308_volatile_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case 0x2f01 ... 0x2f07: + case 0x3000 ... 0x3001: + case 0x3004 ... 0x3005: + case 0x3008: + case 0x300a: + case 0xc000: + return true; + default: + return false; + } +} + +static const struct regmap_config rt1308_sdw_regmap = { + .reg_bits = 32, /* Total register space for SDW */ + .val_bits = 8, /* Total number of bits in register */ + .readable_reg = rt1308_readable_register, /* Readable registers */ + .volatile_reg = rt1308_volatile_register, /* volatile register */ + .max_register = 0xcfff, /* Maximum number of register */ + .reg_defaults = rt1308_reg_defaults, /* Defaults */ + .num_reg_defaults = ARRAY_SIZE(rt1308_reg_defaults), + .cache_type = REGCACHE_RBTREE, + .use_single_read = true, + .use_single_write = true, +}; + +/* Bus clock frequency */ +#define RT1308_CLK_FREQ_9600000HZ 9600000 +#define RT1308_CLK_FREQ_12000000HZ 12000000 +#define RT1308_CLK_FREQ_6000000HZ 6000000 +#define RT1308_CLK_FREQ_4800000HZ 4800000 +#define RT1308_CLK_FREQ_2400000HZ 2400000 +#define RT1308_CLK_FREQ_12288000HZ 12288000 + +static int rt1308_clock_config(struct device *dev) +{ + struct rt1308_sdw_priv *rt1308 = dev_get_drvdata(dev); + unsigned int clk_freq, value; + + clk_freq = (rt1308->params.curr_dr_freq >> 1); + + switch (clk_freq) { + case RT1308_CLK_FREQ_12000000HZ: + value = 0x0; + break; + case RT1308_CLK_FREQ_6000000HZ: + value = 0x1; + break; + case RT1308_CLK_FREQ_9600000HZ: + value = 0x2; + break; + case RT1308_CLK_FREQ_4800000HZ: + value = 0x3; + break; + case RT1308_CLK_FREQ_2400000HZ: + value = 0x4; + break; + case RT1308_CLK_FREQ_12288000HZ: + value = 0x5; + break; + default: + return -EINVAL; + } + + regmap_write(rt1308->regmap, 0xe0, value); + regmap_write(rt1308->regmap, 0xf0, value); + + dev_dbg(dev, "%s complete, clk_freq=%d\n", __func__, clk_freq); + + return 0; +} + +static int rt1308_read_prop(struct sdw_slave *slave) +{ + struct sdw_slave_prop *prop = &slave->prop; + int nval, i, num_of_ports = 1; + u32 bit; + unsigned long addr; + struct sdw_dpn_prop *dpn; + + prop->paging_support = true; + + /* first we need to allocate memory for set bits in port lists */ + prop->source_ports = 0x00; /* BITMAP: 00010100 (not enable yet) */ + prop->sink_ports = 0x2; /* BITMAP: 00000010 */ + + /* for sink */ + nval = hweight32(prop->sink_ports); + num_of_ports += nval; + prop->sink_dpn_prop = devm_kcalloc(&slave->dev, nval, + sizeof(*prop->sink_dpn_prop), + GFP_KERNEL); + if (!prop->sink_dpn_prop) + return -ENOMEM; + + i = 0; + dpn = prop->sink_dpn_prop; + addr = prop->sink_ports; + for_each_set_bit(bit, &addr, 32) { + dpn[i].num = bit; + dpn[i].type = SDW_DPN_FULL; + dpn[i].simple_ch_prep_sm = true; + dpn[i].ch_prep_timeout = 10; + i++; + } + + /* Allocate port_ready based on num_of_ports */ + slave->port_ready = devm_kcalloc(&slave->dev, num_of_ports, + sizeof(*slave->port_ready), + GFP_KERNEL); + if (!slave->port_ready) + return -ENOMEM; + + /* Initialize completion */ + for (i = 0; i < num_of_ports; i++) + init_completion(&slave->port_ready[i]); + + /* set the timeout values */ + prop->clk_stop_timeout = 20; + + dev_dbg(&slave->dev, "%s\n", __func__); + + return 0; +} + +static int rt1308_io_init(struct device *dev, struct sdw_slave *slave) +{ + struct rt1308_sdw_priv *rt1308 = dev_get_drvdata(dev); + int ret = 0; + unsigned int efuse_m_btl_l, efuse_m_btl_r, tmp; + unsigned int efuse_c_btl_l, efuse_c_btl_r; + + if (rt1308->hw_init) + return 0; + + ret = rt1308_read_prop(slave); + if (ret < 0) + goto _io_init_err_; + + if (rt1308->first_init) { + regcache_cache_only(rt1308->regmap, false); + regcache_cache_bypass(rt1308->regmap, true); + } + + /* + * PM runtime is only enabled when a Slave reports as Attached + */ + if (!rt1308->first_init) { + /* set autosuspend parameters */ + pm_runtime_set_autosuspend_delay(&slave->dev, 3000); + pm_runtime_use_autosuspend(&slave->dev); + + /* update count of parent 'active' children */ + pm_runtime_set_active(&slave->dev); + + /* make sure the device does not suspend immediately */ + pm_runtime_mark_last_busy(&slave->dev); + + pm_runtime_enable(&slave->dev); + } + + pm_runtime_get_noresume(&slave->dev); + + /* sw reset */ + regmap_write(rt1308->regmap, RT1308_SDW_RESET, 0); + + /* read efuse */ + regmap_write(rt1308->regmap, 0xc360, 0x01); + regmap_write(rt1308->regmap, 0xc361, 0x80); + regmap_write(rt1308->regmap, 0xc7f0, 0x04); + regmap_write(rt1308->regmap, 0xc7f1, 0xfe); + msleep(100); + regmap_write(rt1308->regmap, 0xc7f0, 0x44); + msleep(20); + regmap_write(rt1308->regmap, 0xc240, 0x10); + + regmap_read(rt1308->regmap, 0xc861, &tmp); + efuse_m_btl_l = tmp; + regmap_read(rt1308->regmap, 0xc860, &tmp); + efuse_m_btl_l = efuse_m_btl_l | (tmp << 8); + regmap_read(rt1308->regmap, 0xc863, &tmp); + efuse_c_btl_l = tmp; + regmap_read(rt1308->regmap, 0xc862, &tmp); + efuse_c_btl_l = efuse_c_btl_l | (tmp << 8); + regmap_read(rt1308->regmap, 0xc871, &tmp); + efuse_m_btl_r = tmp; + regmap_read(rt1308->regmap, 0xc870, &tmp); + efuse_m_btl_r = efuse_m_btl_r | (tmp << 8); + regmap_read(rt1308->regmap, 0xc873, &tmp); + efuse_c_btl_r = tmp; + regmap_read(rt1308->regmap, 0xc872, &tmp); + efuse_c_btl_r = efuse_c_btl_r | (tmp << 8); + dev_info(&slave->dev, "%s m_btl_l=0x%x, m_btl_r=0x%x\n", __func__, + efuse_m_btl_l, efuse_m_btl_r); + dev_info(&slave->dev, "%s c_btl_l=0x%x, c_btl_r=0x%x\n", __func__, + efuse_c_btl_l, efuse_c_btl_r); + + /* initial settings */ + regmap_write(rt1308->regmap, 0xc103, 0xc0); + regmap_write(rt1308->regmap, 0xc030, 0x17); + regmap_write(rt1308->regmap, 0xc031, 0x81); + regmap_write(rt1308->regmap, 0xc032, 0x26); + regmap_write(rt1308->regmap, 0xc040, 0x80); + regmap_write(rt1308->regmap, 0xc041, 0x80); + regmap_write(rt1308->regmap, 0xc042, 0x06); + regmap_write(rt1308->regmap, 0xc052, 0x0a); + regmap_write(rt1308->regmap, 0xc080, 0x0a); + regmap_write(rt1308->regmap, 0xc060, 0x02); + regmap_write(rt1308->regmap, 0xc061, 0x75); + regmap_write(rt1308->regmap, 0xc062, 0x05); + regmap_write(rt1308->regmap, 0xc171, 0x07); + regmap_write(rt1308->regmap, 0xc173, 0x0d); + regmap_write(rt1308->regmap, 0xc311, 0x7f); + regmap_write(rt1308->regmap, 0xc900, 0x90); + regmap_write(rt1308->regmap, 0xc1a0, 0x84); + regmap_write(rt1308->regmap, 0xc1a1, 0x01); + regmap_write(rt1308->regmap, 0xc360, 0x78); + regmap_write(rt1308->regmap, 0xc361, 0x87); + regmap_write(rt1308->regmap, 0xc0a1, 0x71); + regmap_write(rt1308->regmap, 0xc210, 0x00); + regmap_write(rt1308->regmap, 0xc070, 0x00); + regmap_write(rt1308->regmap, 0xc100, 0xd7); + regmap_write(rt1308->regmap, 0xc101, 0xd7); + regmap_write(rt1308->regmap, 0xc300, 0x09); + + if (rt1308->first_init) + regcache_cache_bypass(rt1308->regmap, false); + else + rt1308->first_init = true; + + /* Mark Slave initialization complete */ + rt1308->hw_init = true; + + pm_runtime_mark_last_busy(&slave->dev); + pm_runtime_put_autosuspend(&slave->dev); + + dev_dbg(&slave->dev, "%s hw_init complete\n", __func__); + +_io_init_err_: + return ret; +} + +static int rt1308_update_status(struct sdw_slave *slave, + enum sdw_slave_status status) +{ + struct rt1308_sdw_priv *rt1308 = dev_get_drvdata(&slave->dev); + + /* Update the status */ + rt1308->status = status; + + if (status == SDW_SLAVE_UNATTACHED) + rt1308->hw_init = false; + + /* + * Perform initialization only if slave status is present and + * hw_init flag is false + */ + if (rt1308->hw_init || rt1308->status != SDW_SLAVE_ATTACHED) + return 0; + + /* perform I/O transfers required for Slave initialization */ + return rt1308_io_init(&slave->dev, slave); +} + +static int rt1308_bus_config(struct sdw_slave *slave, + struct sdw_bus_params *params) +{ + struct rt1308_sdw_priv *rt1308 = dev_get_drvdata(&slave->dev); + int ret; + + memcpy(&rt1308->params, params, sizeof(*params)); + + ret = rt1308_clock_config(&slave->dev); + if (ret < 0) + dev_err(&slave->dev, "Invalid clk config"); + + return ret; +} + +static int rt1308_interrupt_callback(struct sdw_slave *slave, + struct sdw_slave_intr_status *status) +{ + dev_dbg(&slave->dev, + "%s control_port_stat=%x", __func__, status->control_port); + + return 0; +} + +static int rt1308_classd_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = + snd_soc_dapm_to_component(w->dapm); + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + msleep(30); + snd_soc_component_update_bits(component, + RT1308_SDW_OFFSET | (RT1308_POWER_STATUS << 4), + 0x3, 0x3); + msleep(40); + break; + case SND_SOC_DAPM_PRE_PMD: + snd_soc_component_update_bits(component, + RT1308_SDW_OFFSET | (RT1308_POWER_STATUS << 4), + 0x3, 0); + usleep_range(150000, 200000); + break; + + default: + break; + } + + return 0; +} + +static const char * const rt1308_rx_data_ch_select[] = { + "LR", + "LL", + "RL", + "RR", +}; + +static SOC_ENUM_SINGLE_DECL(rt1308_rx_data_ch_enum, + RT1308_SDW_OFFSET | (RT1308_DATA_PATH << 4), 0, + rt1308_rx_data_ch_select); + +static const struct snd_kcontrol_new rt1308_snd_controls[] = { + + /* I2S Data Channel Selection */ + SOC_ENUM("RX Channel Select", rt1308_rx_data_ch_enum), +}; + +static const struct snd_kcontrol_new rt1308_sto_dac_l = + SOC_DAPM_SINGLE_AUTODISABLE("Switch", + RT1308_SDW_OFFSET_BYTE3 | (RT1308_DAC_SET << 4), + RT1308_DVOL_MUTE_L_EN_SFT, 1, 1); + +static const struct snd_kcontrol_new rt1308_sto_dac_r = + SOC_DAPM_SINGLE_AUTODISABLE("Switch", + RT1308_SDW_OFFSET_BYTE3 | (RT1308_DAC_SET << 4), + RT1308_DVOL_MUTE_R_EN_SFT, 1, 1); + +static const struct snd_soc_dapm_widget rt1308_dapm_widgets[] = { + /* Audio Interface */ + SND_SOC_DAPM_AIF_IN("AIF1RX", "DP1 Playback", 0, SND_SOC_NOPM, 0, 0), + + /* Supply Widgets */ + SND_SOC_DAPM_SUPPLY("MBIAS20U", + RT1308_SDW_OFFSET | (RT1308_POWER << 4), 7, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("ALDO", + RT1308_SDW_OFFSET | (RT1308_POWER << 4), 6, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("DBG", + RT1308_SDW_OFFSET | (RT1308_POWER << 4), 5, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("DACL", + RT1308_SDW_OFFSET | (RT1308_POWER << 4), 4, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("CLK25M", + RT1308_SDW_OFFSET | (RT1308_POWER << 4), 2, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("ADC_R", + RT1308_SDW_OFFSET | (RT1308_POWER << 4), 1, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("ADC_L", + RT1308_SDW_OFFSET | (RT1308_POWER << 4), 0, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("DAC Power", + RT1308_SDW_OFFSET | (RT1308_POWER << 4), 3, 0, NULL, 0), + + SND_SOC_DAPM_SUPPLY("DLDO", + RT1308_SDW_OFFSET_BYTE1 | (RT1308_POWER << 4), 5, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("VREF", + RT1308_SDW_OFFSET_BYTE1 | (RT1308_POWER << 4), 4, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("MIXER_R", + RT1308_SDW_OFFSET_BYTE1 | (RT1308_POWER << 4), 2, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("MIXER_L", + RT1308_SDW_OFFSET_BYTE1 | (RT1308_POWER << 4), 1, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("MBIAS4U", + RT1308_SDW_OFFSET_BYTE1 | (RT1308_POWER << 4), 0, 0, NULL, 0), + + SND_SOC_DAPM_SUPPLY("PLL2_LDO", + RT1308_SDW_OFFSET_BYTE2 | (RT1308_POWER << 4), 4, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("PLL2B", + RT1308_SDW_OFFSET_BYTE2 | (RT1308_POWER << 4), 3, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("PLL2F", + RT1308_SDW_OFFSET_BYTE2 | (RT1308_POWER << 4), 2, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("PLL2F2", + RT1308_SDW_OFFSET_BYTE2 | (RT1308_POWER << 4), 1, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("PLL2B2", + RT1308_SDW_OFFSET_BYTE2 | (RT1308_POWER << 4), 0, 0, NULL, 0), + + /* Digital Interface */ + SND_SOC_DAPM_DAC("DAC", NULL, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_SWITCH("DAC L", SND_SOC_NOPM, 0, 0, &rt1308_sto_dac_l), + SND_SOC_DAPM_SWITCH("DAC R", SND_SOC_NOPM, 0, 0, &rt1308_sto_dac_r), + + /* Output Lines */ + SND_SOC_DAPM_PGA_E("CLASS D", SND_SOC_NOPM, 0, 0, NULL, 0, + rt1308_classd_event, + SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMU), + SND_SOC_DAPM_OUTPUT("SPOL"), + SND_SOC_DAPM_OUTPUT("SPOR"), +}; + +static const struct snd_soc_dapm_route rt1308_dapm_routes[] = { + + { "DAC", NULL, "AIF1RX" }, + + { "DAC", NULL, "MBIAS20U" }, + { "DAC", NULL, "ALDO" }, + { "DAC", NULL, "DBG" }, + { "DAC", NULL, "DACL" }, + { "DAC", NULL, "CLK25M" }, + { "DAC", NULL, "ADC_R" }, + { "DAC", NULL, "ADC_L" }, + { "DAC", NULL, "DLDO" }, + { "DAC", NULL, "VREF" }, + { "DAC", NULL, "MIXER_R" }, + { "DAC", NULL, "MIXER_L" }, + { "DAC", NULL, "MBIAS4U" }, + { "DAC", NULL, "PLL2_LDO" }, + { "DAC", NULL, "PLL2B" }, + { "DAC", NULL, "PLL2F" }, + { "DAC", NULL, "PLL2F2" }, + { "DAC", NULL, "PLL2B2" }, + + { "DAC L", "Switch", "DAC" }, + { "DAC R", "Switch", "DAC" }, + { "DAC L", NULL, "DAC Power" }, + { "DAC R", NULL, "DAC Power" }, + + { "CLASS D", NULL, "DAC L" }, + { "CLASS D", NULL, "DAC R" }, + { "SPOL", NULL, "CLASS D" }, + { "SPOR", NULL, "CLASS D" }, +}; + +static int rt1308_set_sdw_stream(struct snd_soc_dai *dai, void *sdw_stream, + int direction) +{ + struct sdw_stream_data *stream; + + stream = kzalloc(sizeof(*stream), GFP_KERNEL); + if (!stream) + return -ENOMEM; + + stream->sdw_stream = (struct sdw_stream_runtime *)sdw_stream; + + /* Use tx_mask or rx_mask to configure stream tag and set dma_data */ + if (direction == SNDRV_PCM_STREAM_PLAYBACK) + dai->playback_dma_data = stream; + else + dai->capture_dma_data = stream; + + return 0; +} + +static void rt1308_sdw_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct sdw_stream_data *stream; + + stream = snd_soc_dai_get_dma_data(dai, substream); + snd_soc_dai_set_dma_data(dai, substream, NULL); + kfree(stream); +} + +static int rt1308_sdw_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct rt1308_sdw_priv *rt1308 = + snd_soc_component_get_drvdata(component); + struct sdw_stream_config stream_config; + struct sdw_port_config port_config; + enum sdw_data_direction direction; + struct sdw_stream_data *stream; + int retval, port, num_channels; + + dev_dbg(dai->dev, "%s %s", __func__, dai->name); + stream = snd_soc_dai_get_dma_data(dai, substream); + + if (!stream) + return -EINVAL; + + if (!rt1308->sdw_slave) + return -EINVAL; + + /* SoundWire specific configuration */ + /* port 1 for playback */ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + direction = SDW_DATA_DIR_RX; + port = 1; + } else { + return -EINVAL; + } + + stream_config.frame_rate = params_rate(params); + stream_config.ch_count = params_channels(params); + stream_config.bps = snd_pcm_format_width(params_format(params)); + stream_config.direction = direction; + + num_channels = params_channels(params); + port_config.ch_mask = (1 << (num_channels)) - 1; + port_config.num = port; + + retval = sdw_stream_add_slave(rt1308->sdw_slave, &stream_config, + &port_config, 1, stream->sdw_stream); + if (retval) { + dev_err(dai->dev, "Unable to configure port\n"); + return retval; + } + + return retval; +} + +static int rt1308_sdw_pcm_hw_free(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct rt1308_sdw_priv *rt1308 = + snd_soc_component_get_drvdata(component); + struct sdw_stream_data *stream = + snd_soc_dai_get_dma_data(dai, substream); + + if (!rt1308->sdw_slave) + return -EINVAL; + + sdw_stream_remove_slave(rt1308->sdw_slave, stream->sdw_stream); + return 0; +} + +/* + * slave_ops: callbacks for get_clock_stop_mode, clock_stop and + * port_prep are not defined for now + */ +static struct sdw_slave_ops rt1308_slave_ops = { + .read_prop = rt1308_read_prop, + .interrupt_callback = rt1308_interrupt_callback, + .update_status = rt1308_update_status, + .bus_config = rt1308_bus_config, +}; + +static const struct snd_soc_component_driver soc_component_sdw_rt1308 = { + .controls = rt1308_snd_controls, + .num_controls = ARRAY_SIZE(rt1308_snd_controls), + .dapm_widgets = rt1308_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(rt1308_dapm_widgets), + .dapm_routes = rt1308_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(rt1308_dapm_routes), +}; + +static const struct snd_soc_dai_ops rt1308_aif_dai_ops = { + .hw_params = rt1308_sdw_hw_params, + .hw_free = rt1308_sdw_pcm_hw_free, + .set_sdw_stream = rt1308_set_sdw_stream, + .shutdown = rt1308_sdw_shutdown, +}; + +#define RT1308_STEREO_RATES SNDRV_PCM_RATE_48000 +#define RT1308_FORMATS (SNDRV_PCM_FMTBIT_S8 | \ + SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_S24_LE) + +static struct snd_soc_dai_driver rt1308_sdw_dai[] = { + { + .name = "rt1308-aif", + .playback = { + .stream_name = "DP1 Playback", + .channels_min = 1, + .channels_max = 2, + .rates = RT1308_STEREO_RATES, + .formats = RT1308_FORMATS, + }, + .ops = &rt1308_aif_dai_ops, + }, +}; + +static int rt1308_sdw_init(struct device *dev, struct regmap *regmap, + struct sdw_slave *slave) +{ + struct rt1308_sdw_priv *rt1308; + int ret; + + rt1308 = devm_kzalloc(dev, sizeof(*rt1308), GFP_KERNEL); + if (!rt1308) + return -ENOMEM; + + dev_set_drvdata(dev, rt1308); + rt1308->sdw_slave = slave; + rt1308->regmap = regmap; + + /* + * Mark hw_init to false + * HW init will be performed when device reports present + */ + rt1308->hw_init = false; + rt1308->first_init = false; + + ret = devm_snd_soc_register_component(dev, + &soc_component_sdw_rt1308, + rt1308_sdw_dai, + ARRAY_SIZE(rt1308_sdw_dai)); + + dev_dbg(&slave->dev, "%s\n", __func__); + + return ret; +} + +static int rt1308_sdw_probe(struct sdw_slave *slave, + const struct sdw_device_id *id) +{ + struct regmap *regmap; + + /* Assign ops */ + slave->ops = &rt1308_slave_ops; + + /* Regmap Initialization */ + regmap = devm_regmap_init_sdw(slave, &rt1308_sdw_regmap); + if (!regmap) + return -EINVAL; + + rt1308_sdw_init(&slave->dev, regmap, slave); + + return 0; +} + +static const struct sdw_device_id rt1308_id[] = { + SDW_SLAVE_ENTRY(0x025d, 0x1308, 0), + {}, +}; +MODULE_DEVICE_TABLE(sdw, rt1308_id); + +static int rt1308_dev_suspend(struct device *dev) +{ + struct rt1308_sdw_priv *rt1308 = dev_get_drvdata(dev); + + if (!rt1308->hw_init) + return 0; + + regcache_cache_only(rt1308->regmap, true); + + return 0; +} + +#define RT1308_PROBE_TIMEOUT 2000 + +static int rt1308_dev_resume(struct device *dev) +{ + struct sdw_slave *slave = to_sdw_slave_device(dev); + struct rt1308_sdw_priv *rt1308 = dev_get_drvdata(dev); + unsigned long time; + + if (!rt1308->hw_init) + return 0; + + if (!slave->unattach_request) + goto regmap_sync; + + time = wait_for_completion_timeout(&slave->initialization_complete, + msecs_to_jiffies(RT1308_PROBE_TIMEOUT)); + if (!time) { + dev_err(&slave->dev, "Initialization not complete, timed out\n"); + return -ETIMEDOUT; + } + +regmap_sync: + slave->unattach_request = 0; + regcache_cache_only(rt1308->regmap, false); + regcache_sync_region(rt1308->regmap, 0xc000, 0xcfff); + + return 0; +} + +static const struct dev_pm_ops rt1308_pm = { + SET_SYSTEM_SLEEP_PM_OPS(rt1308_dev_suspend, rt1308_dev_resume) + SET_RUNTIME_PM_OPS(rt1308_dev_suspend, rt1308_dev_resume, NULL) +}; + +static struct sdw_driver rt1308_sdw_driver = { + .driver = { + .name = "rt1308", + .owner = THIS_MODULE, + .pm = &rt1308_pm, + }, + .probe = rt1308_sdw_probe, + .ops = &rt1308_slave_ops, + .id_table = rt1308_id, +}; +module_sdw_driver(rt1308_sdw_driver); + +MODULE_DESCRIPTION("ASoC RT1308 driver SDW"); +MODULE_AUTHOR("Shuming Fan "); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/codecs/rt1308-sdw.h b/sound/soc/codecs/rt1308-sdw.h new file mode 100644 index 00000000000000..e83440be32f240 --- /dev/null +++ b/sound/soc/codecs/rt1308-sdw.h @@ -0,0 +1,169 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * rt1308-sdw.h -- RT1308 ALSA SoC audio driver header + * + * Copyright(c) 2019 Realtek Semiconductor Corp. + */ + +#ifndef __RT1308_SDW_H__ +#define __RT1308_SDW_H__ + +static const struct reg_default rt1308_reg_defaults[] = { + { 0x0000, 0x00 }, + { 0x0001, 0x00 }, + { 0x0002, 0x00 }, + { 0x0003, 0x00 }, + { 0x0004, 0x00 }, + { 0x0005, 0x01 }, + { 0x0020, 0x00 }, + { 0x0022, 0x00 }, + { 0x0023, 0x00 }, + { 0x0024, 0x00 }, + { 0x0025, 0x00 }, + { 0x0026, 0x00 }, + { 0x0030, 0x00 }, + { 0x0032, 0x00 }, + { 0x0033, 0x00 }, + { 0x0034, 0x00 }, + { 0x0035, 0x00 }, + { 0x0036, 0x00 }, + { 0x0040, 0x00 }, + { 0x0041, 0x00 }, + { 0x0042, 0x00 }, + { 0x0043, 0x00 }, + { 0x0044, 0x20 }, + { 0x0045, 0x01 }, + { 0x0046, 0x01 }, + { 0x0048, 0x00 }, + { 0x0049, 0x00 }, + { 0x0050, 0x20 }, + { 0x0051, 0x02 }, + { 0x0052, 0x5D }, + { 0x0053, 0x13 }, + { 0x0054, 0x08 }, + { 0x0055, 0x00 }, + { 0x0060, 0x00 }, + { 0x0070, 0x00 }, + { 0x00E0, 0x00 }, + { 0x00F0, 0x00 }, + { 0x0100, 0x00 }, + { 0x0101, 0x00 }, + { 0x0102, 0x20 }, + { 0x0103, 0x00 }, + { 0x0104, 0x00 }, + { 0x0105, 0x03 }, + { 0x0120, 0x00 }, + { 0x0122, 0x00 }, + { 0x0123, 0x00 }, + { 0x0124, 0x00 }, + { 0x0125, 0x00 }, + { 0x0126, 0x00 }, + { 0x0127, 0x00 }, + { 0x0130, 0x00 }, + { 0x0132, 0x00 }, + { 0x0133, 0x00 }, + { 0x0134, 0x00 }, + { 0x0135, 0x00 }, + { 0x0136, 0x00 }, + { 0x0137, 0x00 }, + { 0x0200, 0x00 }, + { 0x0201, 0x00 }, + { 0x0202, 0x00 }, + { 0x0203, 0x00 }, + { 0x0204, 0x00 }, + { 0x0205, 0x03 }, + { 0x0220, 0x00 }, + { 0x0222, 0x00 }, + { 0x0223, 0x00 }, + { 0x0224, 0x00 }, + { 0x0225, 0x00 }, + { 0x0226, 0x00 }, + { 0x0227, 0x00 }, + { 0x0230, 0x00 }, + { 0x0232, 0x00 }, + { 0x0233, 0x00 }, + { 0x0234, 0x00 }, + { 0x0235, 0x00 }, + { 0x0236, 0x00 }, + { 0x0237, 0x00 }, + { 0x0400, 0x00 }, + { 0x0401, 0x00 }, + { 0x0402, 0x00 }, + { 0x0403, 0x00 }, + { 0x0404, 0x00 }, + { 0x0405, 0x03 }, + { 0x0420, 0x00 }, + { 0x0422, 0x00 }, + { 0x0423, 0x00 }, + { 0x0424, 0x00 }, + { 0x0425, 0x00 }, + { 0x0426, 0x00 }, + { 0x0427, 0x00 }, + { 0x0430, 0x00 }, + { 0x0432, 0x00 }, + { 0x0433, 0x00 }, + { 0x0434, 0x00 }, + { 0x0435, 0x00 }, + { 0x0436, 0x00 }, + { 0x0437, 0x00 }, + { 0x0f00, 0x00 }, + { 0x0f01, 0x00 }, + { 0x0f02, 0x00 }, + { 0x0f03, 0x00 }, + { 0x0f04, 0x00 }, + { 0x0f05, 0x00 }, + { 0x0f20, 0x00 }, + { 0x0f22, 0x00 }, + { 0x0f23, 0x00 }, + { 0x0f24, 0x00 }, + { 0x0f25, 0x00 }, + { 0x0f26, 0x00 }, + { 0x0f27, 0x00 }, + { 0x0f30, 0x00 }, + { 0x0f32, 0x00 }, + { 0x0f33, 0x00 }, + { 0x0f34, 0x00 }, + { 0x0f35, 0x00 }, + { 0x0f36, 0x00 }, + { 0x0f37, 0x00 }, + { 0x2f01, 0x01 }, + { 0x2f02, 0x09 }, + { 0x2f03, 0x00 }, + { 0x2f04, 0x0f }, + { 0x2f05, 0x0b }, + { 0x2f06, 0x01 }, + { 0x2f07, 0x8e }, + { 0x3000, 0x00 }, + { 0x3001, 0x00 }, + { 0x3004, 0x01 }, + { 0x3005, 0x23 }, + { 0x3008, 0x02 }, + { 0x300a, 0x00 }, + { 0xc003 | (RT1308_DAC_SET << 4), 0x00 }, + { 0xc001 | (RT1308_POWER << 4), 0x00 }, + { 0xc002 | (RT1308_POWER << 4), 0x00 }, +}; + +#define RT1308_SDW_OFFSET 0xc000 +#define RT1308_SDW_OFFSET_BYTE0 0xc000 +#define RT1308_SDW_OFFSET_BYTE1 0xc001 +#define RT1308_SDW_OFFSET_BYTE2 0xc002 +#define RT1308_SDW_OFFSET_BYTE3 0xc003 + +#define RT1308_SDW_RESET (RT1308_SDW_OFFSET | (RT1308_RESET << 4)) + +struct rt1308_sdw_priv { + struct snd_soc_component *component; + struct regmap *regmap; + struct sdw_slave *sdw_slave; + enum sdw_slave_status status; + struct sdw_bus_params params; + bool hw_init; + bool first_init; +}; + +struct sdw_stream_data { + struct sdw_stream_runtime *sdw_stream; +}; + +#endif /* __RT1308_SDW_H__ */ diff --git a/sound/soc/codecs/rt1308.c b/sound/soc/codecs/rt1308.c index b75931a69a1cc0..462fd668753bd6 100644 --- a/sound/soc/codecs/rt1308.c +++ b/sound/soc/codecs/rt1308.c @@ -1,10 +1,11 @@ // SPDX-License-Identifier: GPL-2.0 -// -// rt1308.c -- RT1308 ALSA SoC amplifier component driver -// -// Copyright 2019 Realtek Semiconductor Corp. -// Author: Derek Fang -// +/* + * rt1308.c -- RT1308 ALSA SoC amplifier component driver + * + * Copyright 2019 Realtek Semiconductor Corp. + * Author: Derek Fang + * + */ #include #include diff --git a/sound/soc/codecs/rt700-sdw.c b/sound/soc/codecs/rt700-sdw.c new file mode 100644 index 00000000000000..40954f18ddae7c --- /dev/null +++ b/sound/soc/codecs/rt700-sdw.c @@ -0,0 +1,553 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// rt700-sdw.c -- rt700 ALSA SoC audio driver +// +// Copyright(c) 2019 Realtek Semiconductor Corp. +// +// ALC700 ASoC Codec Driver based Intel Dummy SdW codec driver +// + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "rt700.h" +#include "rt700-sdw.h" + +static bool rt700_readable_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case 0x00e0: + case 0x00f0: + case 0x2000 ... 0x200e: + case 0x2012 ... 0x2016: + case 0x201a ... 0x2027: + case 0x2029 ... 0x202a: + case 0x202d ... 0x2034: + case 0x2200 ... 0x2204: + case 0x2206 ... 0x2212: + case 0x2220 ... 0x2223: + case 0x2230 ... 0x2231: + case 0x3000 ... 0x3fff: + case 0x7000 ... 0x7fff: + case 0x8300 ... 0x83ff: + case 0x9c00 ... 0x9cff: + case 0xb900 ... 0xb9ff: + case 0x75201a: + case 0x752045: + case 0x752046: + case 0x752048: + case 0x75204a: + case 0x75206b: + case 0x752080: + case 0x752081: + return true; + default: + return false; + } +} + +static bool rt700_volatile_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case 0x2009: + case 0x2016: + case 0x201b: + case 0x201c: + case 0x201d: + case 0x201f: + case 0x2021: + case 0x2023: + case 0x2230: + case 0x200b ... 0x200e: /* i2c read */ + case 0x2012 ... 0x2015: /* HD-A read */ + case 0x202d ... 0x202f: /* BRA */ + case 0x2201 ... 0x2212: /* i2c debug */ + case 0x2220 ... 0x2223: /* decoded HD-A */ + case 0x9c00 ... 0x9cff: + case 0xb900 ... 0xb9ff: + case 0xff01: + case 0x75201a: + case 0x752046: + case 0x752080: + case 0x752081: + return true; + default: + return false; + } +} + +static int rt700_sdw_read(void *context, unsigned int reg, unsigned int *val) +{ + struct device *dev = context; + struct rt700_priv *rt700 = dev_get_drvdata(dev); + unsigned int sdw_data_3, sdw_data_2, sdw_data_1, sdw_data_0; + unsigned int reg2 = 0, reg3 = 0, reg4 = 0, mask, nid, val2; + unsigned int is_hda_reg = 1, is_index_reg = 0; + int ret; + + if (reg > 0xffff) + is_index_reg = 1; + + mask = reg & 0xf000; + + if (is_index_reg) { /* index registers */ + val2 = reg & 0xff; + reg = reg >> 8; + nid = reg & 0xff; + ret = regmap_write(rt700->sdw_regmap, reg, 0); + if (ret < 0) + return ret; + reg2 = reg + 0x1000; + reg2 |= 0x80; + ret = regmap_write(rt700->sdw_regmap, reg2, val2); + if (ret < 0) + return ret; + + reg3 = RT700_PRIV_DATA_R_H | nid; + ret = regmap_write(rt700->sdw_regmap, + reg3, ((*val >> 8) & 0xff)); + if (ret < 0) + return ret; + reg4 = reg3 + 0x1000; + reg4 |= 0x80; + ret = regmap_write(rt700->sdw_regmap, reg4, (*val & 0xff)); + if (ret < 0) + return ret; + } else if (mask == 0x3000) { + reg += 0x8000; + ret = regmap_write(rt700->sdw_regmap, reg, *val); + if (ret < 0) + return ret; + } else if (mask == 0x7000) { + reg += 0x2000; + reg |= 0x800; + ret = regmap_write(rt700->sdw_regmap, + reg, ((*val >> 8) & 0xff)); + if (ret < 0) + return ret; + reg2 = reg + 0x1000; + reg2 |= 0x80; + ret = regmap_write(rt700->sdw_regmap, reg2, (*val & 0xff)); + if (ret < 0) + return ret; + } else if ((reg & 0xff00) == 0x8300) { /* for R channel */ + reg2 = reg - 0x1000; + reg2 &= ~0x80; + ret = regmap_write(rt700->sdw_regmap, + reg2, ((*val >> 8) & 0xff)); + if (ret < 0) + return ret; + ret = regmap_write(rt700->sdw_regmap, reg, (*val & 0xff)); + if (ret < 0) + return ret; + } else if (mask == 0x9000) { + ret = regmap_write(rt700->sdw_regmap, + reg, ((*val >> 8) & 0xff)); + if (ret < 0) + return ret; + reg2 = reg + 0x1000; + reg2 |= 0x80; + ret = regmap_write(rt700->sdw_regmap, reg2, (*val & 0xff)); + if (ret < 0) + return ret; + } else if (mask == 0xb000) { + ret = regmap_write(rt700->sdw_regmap, reg, *val); + if (ret < 0) + return ret; + } else { + ret = regmap_read(rt700->sdw_regmap, reg, val); + if (ret < 0) + return ret; + is_hda_reg = 0; + } + + if (is_hda_reg || is_index_reg) { + sdw_data_3 = 0; + sdw_data_2 = 0; + sdw_data_1 = 0; + sdw_data_0 = 0; + ret = regmap_read(rt700->sdw_regmap, + RT700_READ_HDA_3, &sdw_data_3); + if (ret < 0) + return ret; + ret = regmap_read(rt700->sdw_regmap, + RT700_READ_HDA_2, &sdw_data_2); + if (ret < 0) + return ret; + ret = regmap_read(rt700->sdw_regmap, + RT700_READ_HDA_1, &sdw_data_1); + if (ret < 0) + return ret; + ret = regmap_read(rt700->sdw_regmap, + RT700_READ_HDA_0, &sdw_data_0); + if (ret < 0) + return ret; + *val = ((sdw_data_3 & 0xff) << 24) | + ((sdw_data_2 & 0xff) << 16) | + ((sdw_data_1 & 0xff) << 8) | (sdw_data_0 & 0xff); + } + + if (is_hda_reg == 0) + dev_dbg(dev, "[%s] %04x => %08x\n", __func__, reg, *val); + else if (is_index_reg) + dev_dbg(dev, "[%s] %04x %04x %04x %04x => %08x\n", + __func__, reg, reg2, reg3, reg4, *val); + else + dev_dbg(dev, "[%s] %04x %04x => %08x\n", + __func__, reg, reg2, *val); + + return 0; +} + +static int rt700_sdw_write(void *context, unsigned int reg, unsigned int val) +{ + struct device *dev = context; + struct rt700_priv *rt700 = dev_get_drvdata(dev); + unsigned int reg2 = 0, reg3, reg4, nid, mask, val2; + unsigned int is_index_reg = 0; + int ret; + + if (reg > 0xffff) + is_index_reg = 1; + + mask = reg & 0xf000; + + if (is_index_reg) { /* index registers */ + val2 = reg & 0xff; + reg = reg >> 8; + nid = reg & 0xff; + ret = regmap_write(rt700->sdw_regmap, reg, 0); + if (ret < 0) + return ret; + reg2 = reg + 0x1000; + reg2 |= 0x80; + ret = regmap_write(rt700->sdw_regmap, reg2, val2); + if (ret < 0) + return ret; + + reg3 = RT700_PRIV_DATA_W_H | nid; + ret = regmap_write(rt700->sdw_regmap, + reg3, ((val >> 8) & 0xff)); + if (ret < 0) + return ret; + reg4 = reg3 + 0x1000; + reg4 |= 0x80; + ret = regmap_write(rt700->sdw_regmap, reg4, (val & 0xff)); + if (ret < 0) + return ret; + is_index_reg = 1; + } else if (reg < 0x4fff) { + ret = regmap_write(rt700->sdw_regmap, reg, val); + if (ret < 0) + return ret; + } else if (reg == 0xff01) { + ret = regmap_write(rt700->sdw_regmap, reg, val); + if (ret < 0) + return ret; + } else if (mask == 0x7000) { + ret = regmap_write(rt700->sdw_regmap, + reg, ((val >> 8) & 0xff)); + if (ret < 0) + return ret; + reg2 = reg + 0x1000; + reg2 |= 0x80; + ret = regmap_write(rt700->sdw_regmap, reg2, (val & 0xff)); + if (ret < 0) + return ret; + } else if ((reg & 0xff00) == 0x8300) { /* for R channel */ + reg2 = reg - 0x1000; + reg2 &= ~0x80; + ret = regmap_write(rt700->sdw_regmap, + reg2, ((val >> 8) & 0xff)); + if (ret < 0) + return ret; + ret = regmap_write(rt700->sdw_regmap, reg, (val & 0xff)); + if (ret < 0) + return ret; + } + + if (reg2 == 0) + dev_dbg(dev, "[%s] %04x <= %04x\n", __func__, reg, val); + else if (is_index_reg) + dev_dbg(dev, "[%s] %04x %04x %04x %04x <= %04x %04x\n", + __func__, reg, reg2, reg3, reg4, val2, val); + else + dev_dbg(dev, "[%s] %04x %04x <= %04x\n", + __func__, reg, reg2, val); + + return 0; +} + +static const struct regmap_config rt700_regmap = { + .reg_bits = 24, + .val_bits = 32, + .readable_reg = rt700_readable_register, /* Readable registers */ + .volatile_reg = rt700_volatile_register, /* volatile register */ + .max_register = 0x755800, /* Maximum number of register */ + .reg_defaults = rt700_reg_defaults, /* Defaults */ + .num_reg_defaults = ARRAY_SIZE(rt700_reg_defaults), + .cache_type = REGCACHE_RBTREE, + .use_single_read = true, + .use_single_write = true, + .reg_read = rt700_sdw_read, + .reg_write = rt700_sdw_write, +}; + +static const struct regmap_config rt700_sdw_regmap = { + .name = "sdw", + .reg_bits = 32, /* Total register space for SDW */ + .val_bits = 8, /* Total number of bits in register */ + .readable_reg = rt700_readable_register, /* Readable registers */ + .max_register = 0xff01, /* Maximum number of register */ + .cache_type = REGCACHE_NONE, + .use_single_read = true, + .use_single_write = true, +}; + +static int rt700_update_status(struct sdw_slave *slave, + enum sdw_slave_status status) +{ + struct rt700_priv *rt700 = dev_get_drvdata(&slave->dev); + + /* Update the status */ + rt700->status = status; + + if (status == SDW_SLAVE_UNATTACHED) + rt700->hw_init = false; + + /* + * Perform initialization only if slave status is present and + * hw_init flag is false + */ + if (rt700->hw_init || rt700->status != SDW_SLAVE_ATTACHED) + return 0; + + /* perform I/O transfers required for Slave initialization */ + return rt700_io_init(&slave->dev, slave); +} + +static int rt700_read_prop(struct sdw_slave *slave) +{ + struct sdw_slave_prop *prop = &slave->prop; + int nval, i, num_of_ports = 1; + u32 bit; + unsigned long addr; + struct sdw_dpn_prop *dpn; + + prop->paging_support = false; + + /* first we need to allocate memory for set bits in port lists */ + prop->source_ports = 0x14; /* BITMAP: 00010100 */ + prop->sink_ports = 0xA; /* BITMAP: 00001010 */ + + nval = hweight32(prop->source_ports); + num_of_ports += nval; + prop->src_dpn_prop = devm_kcalloc(&slave->dev, nval, + sizeof(*prop->src_dpn_prop), + GFP_KERNEL); + if (!prop->src_dpn_prop) + return -ENOMEM; + + i = 0; + dpn = prop->src_dpn_prop; + addr = prop->source_ports; + for_each_set_bit(bit, &addr, 32) { + dpn[i].num = bit; + dpn[i].type = SDW_DPN_FULL; + dpn[i].simple_ch_prep_sm = true; + dpn[i].ch_prep_timeout = 10; + i++; + } + + /* do this again for sink now */ + nval = hweight32(prop->sink_ports); + num_of_ports += nval; + prop->sink_dpn_prop = devm_kcalloc(&slave->dev, nval, + sizeof(*prop->sink_dpn_prop), + GFP_KERNEL); + if (!prop->sink_dpn_prop) + return -ENOMEM; + + i = 0; + dpn = prop->sink_dpn_prop; + addr = prop->sink_ports; + for_each_set_bit(bit, &addr, 32) { + dpn[i].num = bit; + dpn[i].type = SDW_DPN_FULL; + dpn[i].simple_ch_prep_sm = true; + dpn[i].ch_prep_timeout = 10; + i++; + } + + /* Allocate port_ready based on num_of_ports */ + slave->port_ready = devm_kcalloc(&slave->dev, num_of_ports, + sizeof(*slave->port_ready), + GFP_KERNEL); + if (!slave->port_ready) + return -ENOMEM; + + /* Initialize completion */ + for (i = 0; i < num_of_ports; i++) + init_completion(&slave->port_ready[i]); + + /* set the timeout values */ + prop->clk_stop_timeout = 20; + + /* wake-up event */ + prop->wake_capable = 1; + + return 0; +} + +static int rt700_bus_config(struct sdw_slave *slave, + struct sdw_bus_params *params) +{ + struct rt700_priv *rt700 = dev_get_drvdata(&slave->dev); + int ret; + + memcpy(&rt700->params, params, sizeof(*params)); + + ret = rt700_clock_config(&slave->dev); + if (ret < 0) + dev_err(&slave->dev, "Invalid clk config"); + + return ret; +} + +static int rt700_interrupt_callback(struct sdw_slave *slave, + struct sdw_slave_intr_status *status) +{ + struct rt700_priv *rt700 = dev_get_drvdata(&slave->dev); + + dev_dbg(&slave->dev, + "%s control_port_stat=%x", __func__, status->control_port); + + if (status->control_port & 0x4) { + mod_delayed_work(system_power_efficient_wq, + &rt700->jack_detect_work, msecs_to_jiffies(250)); + } + + return 0; +} + +/* + * slave_ops: callbacks for get_clock_stop_mode, clock_stop and + * port_prep are not defined for now + */ +static struct sdw_slave_ops rt700_slave_ops = { + .read_prop = rt700_read_prop, + .interrupt_callback = rt700_interrupt_callback, + .update_status = rt700_update_status, + .bus_config = rt700_bus_config, +}; + +static int rt700_sdw_probe(struct sdw_slave *slave, + const struct sdw_device_id *id) +{ + struct regmap *sdw_regmap, *regmap; + + /* Assign ops */ + slave->ops = &rt700_slave_ops; + + /* Regmap Initialization */ + sdw_regmap = devm_regmap_init_sdw(slave, &rt700_sdw_regmap); + if (!sdw_regmap) + return -EINVAL; + + regmap = devm_regmap_init(&slave->dev, NULL, + &slave->dev, &rt700_regmap); + if (!regmap) + return -EINVAL; + + rt700_init(&slave->dev, sdw_regmap, regmap, slave); + + return 0; +} + +static int rt700_sdw_remove(struct sdw_slave *slave) +{ + struct rt700_priv *rt700 = dev_get_drvdata(&slave->dev); + + if (rt700 && rt700->hw_init) { + cancel_delayed_work(&rt700->jack_detect_work); + cancel_delayed_work(&rt700->jack_btn_check_work); + } + + return 0; +} + +static const struct sdw_device_id rt700_id[] = { + SDW_SLAVE_ENTRY(0x025d, 0x700, 0), + {}, +}; +MODULE_DEVICE_TABLE(sdw, rt700_id); + +static int rt700_dev_suspend(struct device *dev) +{ + struct rt700_priv *rt700 = dev_get_drvdata(dev); + + if (!rt700->hw_init) + return 0; + + regcache_cache_only(rt700->regmap, true); + + return 0; +} + +#define RT700_PROBE_TIMEOUT 2000 + +static int rt700_dev_resume(struct device *dev) +{ + struct sdw_slave *slave = to_sdw_slave_device(dev); + struct rt700_priv *rt700 = dev_get_drvdata(dev); + unsigned long time; + + if (!rt700->hw_init) + return 0; + + if (!slave->unattach_request) + goto regmap_sync; + + time = wait_for_completion_timeout(&slave->initialization_complete, + msecs_to_jiffies(RT700_PROBE_TIMEOUT)); + if (!time) { + dev_err(&slave->dev, "Initialization not complete, timed out\n"); + return -ETIMEDOUT; + } + +regmap_sync: + slave->unattach_request = 0; + regcache_cache_only(rt700->regmap, false); + regcache_sync_region(rt700->regmap, 0x3000, 0x8fff); + regcache_sync_region(rt700->regmap, 0x752010, 0x75206b); + + return 0; +} + +static const struct dev_pm_ops rt700_pm = { + SET_SYSTEM_SLEEP_PM_OPS(rt700_dev_suspend, rt700_dev_resume) + SET_RUNTIME_PM_OPS(rt700_dev_suspend, rt700_dev_resume, NULL) +}; + +static struct sdw_driver rt700_sdw_driver = { + .driver = { + .name = "rt700", + .owner = THIS_MODULE, + .pm = &rt700_pm, + }, + .probe = rt700_sdw_probe, + .remove = rt700_sdw_remove, + .ops = &rt700_slave_ops, + .id_table = rt700_id, +}; +module_sdw_driver(rt700_sdw_driver); + +MODULE_DESCRIPTION("ASoC RT700 driver SDW"); +MODULE_AUTHOR("Shuming Fan "); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/codecs/rt700-sdw.h b/sound/soc/codecs/rt700-sdw.h new file mode 100644 index 00000000000000..4ad0dcfd16fdb7 --- /dev/null +++ b/sound/soc/codecs/rt700-sdw.h @@ -0,0 +1,335 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * rt700-sdw.h -- RT700 ALSA SoC audio driver header + * + * Copyright(c) 2019 Realtek Semiconductor Corp. + */ + +#ifndef __RT700_SDW_H__ +#define __RT700_SDW_H__ + +static const struct reg_default rt700_reg_defaults[] = { + { 0x0000, 0x0000 }, + { 0x0001, 0x0000 }, + { 0x0002, 0x0000 }, + { 0x0003, 0x0000 }, + { 0x0004, 0x0000 }, + { 0x0005, 0x0001 }, + { 0x0020, 0x0000 }, + { 0x0022, 0x0000 }, + { 0x0023, 0x0000 }, + { 0x0024, 0x0000 }, + { 0x0025, 0x0000 }, + { 0x0026, 0x0000 }, + { 0x0030, 0x0000 }, + { 0x0032, 0x0000 }, + { 0x0033, 0x0000 }, + { 0x0034, 0x0000 }, + { 0x0035, 0x0000 }, + { 0x0036, 0x0000 }, + { 0x0040, 0x0000 }, + { 0x0041, 0x0000 }, + { 0x0042, 0x0000 }, + { 0x0043, 0x0000 }, + { 0x0044, 0x0020 }, + { 0x0045, 0x0001 }, + { 0x0046, 0x0000 }, + { 0x0050, 0x0000 }, + { 0x0051, 0x0000 }, + { 0x0052, 0x0000 }, + { 0x0053, 0x0000 }, + { 0x0054, 0x0000 }, + { 0x0055, 0x0000 }, + { 0x0060, 0x0000 }, + { 0x0070, 0x0000 }, + { 0x00e0, 0x0000 }, + { 0x00f0, 0x0000 }, + { 0x0100, 0x0000 }, + { 0x0101, 0x0000 }, + { 0x0102, 0x0000 }, + { 0x0103, 0x0000 }, + { 0x0104, 0x0000 }, + { 0x0105, 0x0000 }, + { 0x0120, 0x0000 }, + { 0x0121, 0x0000 }, + { 0x0122, 0x0000 }, + { 0x0123, 0x0000 }, + { 0x0124, 0x0000 }, + { 0x0125, 0x0000 }, + { 0x0126, 0x0000 }, + { 0x0127, 0x0000 }, + { 0x0130, 0x0000 }, + { 0x0131, 0x0000 }, + { 0x0132, 0x0000 }, + { 0x0133, 0x0000 }, + { 0x0134, 0x0000 }, + { 0x0135, 0x0000 }, + { 0x0136, 0x0000 }, + { 0x0137, 0x0000 }, + { 0x0200, 0x0000 }, + { 0x0201, 0x0000 }, + { 0x0202, 0x0000 }, + { 0x0203, 0x0000 }, + { 0x0204, 0x0000 }, + { 0x0205, 0x0000 }, + { 0x0220, 0x0000 }, + { 0x0221, 0x0000 }, + { 0x0222, 0x0000 }, + { 0x0223, 0x0000 }, + { 0x0224, 0x0000 }, + { 0x0225, 0x0000 }, + { 0x0226, 0x0000 }, + { 0x0227, 0x0000 }, + { 0x0230, 0x0000 }, + { 0x0231, 0x0000 }, + { 0x0232, 0x0000 }, + { 0x0233, 0x0000 }, + { 0x0234, 0x0000 }, + { 0x0235, 0x0000 }, + { 0x0236, 0x0000 }, + { 0x0237, 0x0000 }, + { 0x0300, 0x0000 }, + { 0x0301, 0x0000 }, + { 0x0302, 0x0000 }, + { 0x0303, 0x0000 }, + { 0x0304, 0x0000 }, + { 0x0305, 0x0000 }, + { 0x0320, 0x0000 }, + { 0x0321, 0x0000 }, + { 0x0322, 0x0000 }, + { 0x0323, 0x0000 }, + { 0x0324, 0x0000 }, + { 0x0325, 0x0000 }, + { 0x0326, 0x0000 }, + { 0x0327, 0x0000 }, + { 0x0330, 0x0000 }, + { 0x0331, 0x0000 }, + { 0x0332, 0x0000 }, + { 0x0333, 0x0000 }, + { 0x0334, 0x0000 }, + { 0x0335, 0x0000 }, + { 0x0336, 0x0000 }, + { 0x0337, 0x0000 }, + { 0x0400, 0x0000 }, + { 0x0401, 0x0000 }, + { 0x0402, 0x0000 }, + { 0x0403, 0x0000 }, + { 0x0404, 0x0000 }, + { 0x0405, 0x0000 }, + { 0x0420, 0x0000 }, + { 0x0421, 0x0000 }, + { 0x0422, 0x0000 }, + { 0x0423, 0x0000 }, + { 0x0424, 0x0000 }, + { 0x0425, 0x0000 }, + { 0x0426, 0x0000 }, + { 0x0427, 0x0000 }, + { 0x0430, 0x0000 }, + { 0x0431, 0x0000 }, + { 0x0432, 0x0000 }, + { 0x0433, 0x0000 }, + { 0x0434, 0x0000 }, + { 0x0435, 0x0000 }, + { 0x0436, 0x0000 }, + { 0x0437, 0x0000 }, + { 0x0500, 0x0000 }, + { 0x0501, 0x0000 }, + { 0x0502, 0x0000 }, + { 0x0503, 0x0000 }, + { 0x0504, 0x0000 }, + { 0x0505, 0x0000 }, + { 0x0520, 0x0000 }, + { 0x0521, 0x0000 }, + { 0x0522, 0x0000 }, + { 0x0523, 0x0000 }, + { 0x0524, 0x0000 }, + { 0x0525, 0x0000 }, + { 0x0526, 0x0000 }, + { 0x0527, 0x0000 }, + { 0x0530, 0x0000 }, + { 0x0531, 0x0000 }, + { 0x0532, 0x0000 }, + { 0x0533, 0x0000 }, + { 0x0534, 0x0000 }, + { 0x0535, 0x0000 }, + { 0x0536, 0x0000 }, + { 0x0537, 0x0000 }, + { 0x0600, 0x0000 }, + { 0x0601, 0x0000 }, + { 0x0602, 0x0000 }, + { 0x0603, 0x0000 }, + { 0x0604, 0x0000 }, + { 0x0605, 0x0000 }, + { 0x0620, 0x0000 }, + { 0x0621, 0x0000 }, + { 0x0622, 0x0000 }, + { 0x0623, 0x0000 }, + { 0x0624, 0x0000 }, + { 0x0625, 0x0000 }, + { 0x0626, 0x0000 }, + { 0x0627, 0x0000 }, + { 0x0630, 0x0000 }, + { 0x0631, 0x0000 }, + { 0x0632, 0x0000 }, + { 0x0633, 0x0000 }, + { 0x0634, 0x0000 }, + { 0x0635, 0x0000 }, + { 0x0636, 0x0000 }, + { 0x0637, 0x0000 }, + { 0x0700, 0x0000 }, + { 0x0701, 0x0000 }, + { 0x0702, 0x0000 }, + { 0x0703, 0x0000 }, + { 0x0704, 0x0000 }, + { 0x0705, 0x0000 }, + { 0x0720, 0x0000 }, + { 0x0721, 0x0000 }, + { 0x0722, 0x0000 }, + { 0x0723, 0x0000 }, + { 0x0724, 0x0000 }, + { 0x0725, 0x0000 }, + { 0x0726, 0x0000 }, + { 0x0727, 0x0000 }, + { 0x0730, 0x0000 }, + { 0x0731, 0x0000 }, + { 0x0732, 0x0000 }, + { 0x0733, 0x0000 }, + { 0x0734, 0x0000 }, + { 0x0735, 0x0000 }, + { 0x0736, 0x0000 }, + { 0x0737, 0x0000 }, + { 0x0800, 0x0000 }, + { 0x0801, 0x0000 }, + { 0x0802, 0x0000 }, + { 0x0803, 0x0000 }, + { 0x0804, 0x0000 }, + { 0x0805, 0x0000 }, + { 0x0820, 0x0000 }, + { 0x0821, 0x0000 }, + { 0x0822, 0x0000 }, + { 0x0823, 0x0000 }, + { 0x0824, 0x0000 }, + { 0x0825, 0x0000 }, + { 0x0826, 0x0000 }, + { 0x0827, 0x0000 }, + { 0x0830, 0x0000 }, + { 0x0831, 0x0000 }, + { 0x0832, 0x0000 }, + { 0x0833, 0x0000 }, + { 0x0834, 0x0000 }, + { 0x0835, 0x0000 }, + { 0x0836, 0x0000 }, + { 0x0837, 0x0000 }, + { 0x0f00, 0x0000 }, + { 0x0f01, 0x0000 }, + { 0x0f02, 0x0000 }, + { 0x0f03, 0x0000 }, + { 0x0f04, 0x0000 }, + { 0x0f05, 0x0000 }, + { 0x0f20, 0x0000 }, + { 0x0f21, 0x0000 }, + { 0x0f22, 0x0000 }, + { 0x0f23, 0x0000 }, + { 0x0f24, 0x0000 }, + { 0x0f25, 0x0000 }, + { 0x0f26, 0x0000 }, + { 0x0f27, 0x0000 }, + { 0x0f30, 0x0000 }, + { 0x0f31, 0x0000 }, + { 0x0f32, 0x0000 }, + { 0x0f33, 0x0000 }, + { 0x0f34, 0x0000 }, + { 0x0f35, 0x0000 }, + { 0x0f36, 0x0000 }, + { 0x0f37, 0x0000 }, + { 0x2000, 0x0000 }, + { 0x2001, 0x0000 }, + { 0x2002, 0x0000 }, + { 0x2003, 0x0000 }, + { 0x2004, 0x0000 }, + { 0x2005, 0x0000 }, + { 0x2006, 0x0000 }, + { 0x2007, 0x0000 }, + { 0x2008, 0x0000 }, + { 0x2009, 0x0003 }, + { 0x200a, 0x0003 }, + { 0x200b, 0x0000 }, + { 0x200c, 0x0000 }, + { 0x200d, 0x0000 }, + { 0x200e, 0x0000 }, + { 0x2012, 0x0000 }, + { 0x2013, 0x0000 }, + { 0x2014, 0x0000 }, + { 0x2015, 0x0000 }, + { 0x2016, 0x0000 }, + { 0x201a, 0x0000 }, + { 0x201b, 0x0000 }, + { 0x201c, 0x0000 }, + { 0x201d, 0x0000 }, + { 0x201e, 0x0000 }, + { 0x201f, 0x0000 }, + { 0x2020, 0x0000 }, + { 0x2021, 0x0000 }, + { 0x2022, 0x0000 }, + { 0x2023, 0x0000 }, + { 0x2024, 0x0000 }, + { 0x2025, 0x0002 }, + { 0x2026, 0x0000 }, + { 0x2027, 0x0000 }, + { 0x2029, 0x0000 }, + { 0x202a, 0x0000 }, + { 0x202d, 0x0000 }, + { 0x202e, 0x0000 }, + { 0x202f, 0x0000 }, + { 0x2030, 0x0000 }, + { 0x2031, 0x0000 }, + { 0x2032, 0x0000 }, + { 0x2033, 0x0000 }, + { 0x2034, 0x0000 }, + { 0x2200, 0x0000 }, + { 0x2201, 0x0000 }, + { 0x2202, 0x0000 }, + { 0x2203, 0x0000 }, + { 0x2204, 0x0000 }, + { 0x2206, 0x0000 }, + { 0x2207, 0x0000 }, + { 0x2208, 0x0000 }, + { 0x2209, 0x0000 }, + { 0x220a, 0x0000 }, + { 0x220b, 0x0000 }, + { 0x220c, 0x0000 }, + { 0x220d, 0x0000 }, + { 0x220e, 0x0000 }, + { 0x220f, 0x0000 }, + { 0x2211, 0x0000 }, + { 0x2212, 0x0000 }, + { 0x2220, 0x0000 }, + { 0x2221, 0x0000 }, + { 0x2222, 0x0000 }, + { 0x2223, 0x0000 }, + { 0x2230, 0x0000 }, + { 0x2231, 0x0000 }, + { 0x3121, 0x0001 }, + { 0x3122, 0x0000 }, + { 0x3123, 0x0000 }, + { 0x7303, 0x0057 }, + { 0x7303, 0x0057 }, + { 0x8383, 0x0057 }, + { 0x7308, 0x0097 }, + { 0x8388, 0x0097 }, + { 0x7309, 0x0097 }, + { 0x8389, 0x0097 }, + { 0x7312, 0x0000 }, + { 0x8392, 0x0000 }, + { 0x7313, 0x0000 }, + { 0x8393, 0x0000 }, + { 0x7319, 0x0000 }, + { 0x8399, 0x0000 }, + { 0x75201a, 0x8003 }, + { 0x752045, 0x5289 }, + { 0x752048, 0xd049 }, + { 0x75204a, 0xa83b }, + { 0x75206b, 0x5064 }, +}; + +#endif /* __RT700_H__ */ diff --git a/sound/soc/codecs/rt700.c b/sound/soc/codecs/rt700.c new file mode 100644 index 00000000000000..44a8cafbad5bfa --- /dev/null +++ b/sound/soc/codecs/rt700.c @@ -0,0 +1,1236 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// rt700.c -- rt700 ALSA SoC audio driver +// +// Copyright(c) 2019 Realtek Semiconductor Corp. +// +// + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "rt700.h" + +static int rt700_index_write(struct regmap *regmap, + unsigned int reg, unsigned int value) +{ + int ret; + unsigned int addr = (RT700_PRIV_INDEX_W_H << 8) | reg; + + ret = regmap_write(regmap, addr, value); + if (ret < 0) + pr_err("Failed to set private value: %06x <= %04x ret=%d\n", + addr, value, ret); + + return ret; +} + +static int rt700_index_read(struct regmap *regmap, + unsigned int reg, unsigned int *value) +{ + int ret; + unsigned int addr = (RT700_PRIV_INDEX_W_H << 8) | reg; + + *value = 0; + ret = regmap_read(regmap, addr, value); + if (ret < 0) + pr_err("Failed to get private value: %06x => %04x ret=%d\n", + addr, *value, ret); + + return ret; +} + +static unsigned int rt700_button_detect(struct rt700_priv *rt700) +{ + unsigned int btn_type = 0, val80, val81; + int ret; + + ret = rt700_index_read(rt700->regmap, RT700_IRQ_FLAG_TABLE1, &val80); + if (ret < 0) + goto read_error; + ret = rt700_index_read(rt700->regmap, RT700_IRQ_FLAG_TABLE2, &val81); + if (ret < 0) + goto read_error; + + val80 &= 0x0381; + val81 &= 0xff00; + + switch (val80) { + case 0x0200: + case 0x0100: + case 0x0080: + btn_type |= SND_JACK_BTN_0; + break; + case 0x0001: + btn_type |= SND_JACK_BTN_3; + break; + } + switch (val81) { + case 0x8000: + case 0x4000: + case 0x2000: + btn_type |= SND_JACK_BTN_1; + break; + case 0x1000: + case 0x0800: + case 0x0400: + btn_type |= SND_JACK_BTN_2; + break; + case 0x0200: + case 0x0100: + btn_type |= SND_JACK_BTN_3; + break; + } +read_error: + return btn_type; +} + +static int rt700_headset_detect(struct rt700_priv *rt700) +{ + unsigned int buf, loop = 0; + int ret; + + ret = rt700_index_read(rt700->regmap, + RT700_COMBO_JACK_AUTO_CTL2, &buf); + if (ret < 0) + goto io_error; + + while (loop < 500 && + (buf & RT700_COMBOJACK_AUTO_DET_STATUS) == 0) { + loop++; + + usleep_range(9000, 10000); + ret = rt700_index_read(rt700->regmap, + RT700_COMBO_JACK_AUTO_CTL2, &buf); + if (ret < 0) + goto io_error; + } + + if (loop >= 500) + goto to_error; + + if (buf & RT700_COMBOJACK_AUTO_DET_TRS) + rt700->jack_type = SND_JACK_HEADPHONE; + else if ((buf & RT700_COMBOJACK_AUTO_DET_CTIA) || + (buf & RT700_COMBOJACK_AUTO_DET_OMTP)) + rt700->jack_type = SND_JACK_HEADSET; + + return 0; + +to_error: + ret = -ETIMEDOUT; + pr_err_ratelimited("Time-out error in %s\n", __func__); + return ret; +io_error: + pr_err_ratelimited("IO error in %s, ret %d\n", __func__, ret); + return ret; +} + +static void rt700_jack_detect_handler(struct work_struct *work) +{ + struct rt700_priv *rt700 = + container_of(work, struct rt700_priv, jack_detect_work.work); + int btn_type = 0, ret; + unsigned int jack_status = 0, reg; + + if (!rt700->hs_jack) + return; + + if (!rt700->component->card->instantiated) + return; + + reg = RT700_VERB_GET_PIN_SENSE | RT700_HP_OUT; + ret = regmap_read(rt700->regmap, reg, &jack_status); + if (ret < 0) + goto io_error; + + /* pin attached */ + if (jack_status & (1 << 31)) { + /* jack in */ + if (rt700->jack_type == 0) { + ret = rt700_headset_detect(rt700); + if (ret < 0) + return; + if (rt700->jack_type == SND_JACK_HEADSET) + btn_type = rt700_button_detect(rt700); + } else if (rt700->jack_type == SND_JACK_HEADSET) { + /* jack is already in, report button event */ + btn_type = rt700_button_detect(rt700); + } + } else { + /* jack out */ + rt700->jack_type = 0; + } + + dev_dbg(&rt700->slave->dev, + "in %s, jack_type=0x%x\n", __func__, rt700->jack_type); + dev_dbg(&rt700->slave->dev, + "in %s, btn_type=0x%x\n", __func__, btn_type); + + snd_soc_jack_report(rt700->hs_jack, rt700->jack_type | btn_type, + SND_JACK_HEADSET | + SND_JACK_BTN_0 | SND_JACK_BTN_1 | + SND_JACK_BTN_2 | SND_JACK_BTN_3); + + if (btn_type) { + /* button released */ + snd_soc_jack_report(rt700->hs_jack, rt700->jack_type, + SND_JACK_HEADSET | + SND_JACK_BTN_0 | SND_JACK_BTN_1 | + SND_JACK_BTN_2 | SND_JACK_BTN_3); + + mod_delayed_work(system_power_efficient_wq, + &rt700->jack_btn_check_work, msecs_to_jiffies(200)); + } + + return; + +io_error: + pr_err_ratelimited("IO error in %s, ret %d\n", __func__, ret); +} + +static void rt700_btn_check_handler(struct work_struct *work) +{ + struct rt700_priv *rt700 = container_of(work, struct rt700_priv, + jack_btn_check_work.work); + int btn_type = 0, ret; + unsigned int jack_status = 0, reg; + + reg = RT700_VERB_GET_PIN_SENSE | RT700_HP_OUT; + ret = regmap_read(rt700->regmap, reg, &jack_status); + if (ret < 0) + goto io_error; + + /* pin attached */ + if (jack_status & (1 << 31)) { + if (rt700->jack_type == SND_JACK_HEADSET) { + /* jack is already in, report button event */ + btn_type = rt700_button_detect(rt700); + } + } else { + rt700->jack_type = 0; + } + + /* cbj comparator */ + ret = rt700_index_read(rt700->regmap, RT700_COMBO_JACK_AUTO_CTL2, ®); + if (ret < 0) + goto io_error; + + if ((reg & 0xf0) == 0xf0) + btn_type = 0; + + dev_dbg(&rt700->slave->dev, + "%s, btn_type=0x%x\n", __func__, btn_type); + snd_soc_jack_report(rt700->hs_jack, rt700->jack_type | btn_type, + SND_JACK_HEADSET | + SND_JACK_BTN_0 | SND_JACK_BTN_1 | + SND_JACK_BTN_2 | SND_JACK_BTN_3); + + if (btn_type) { + /* button released */ + snd_soc_jack_report(rt700->hs_jack, rt700->jack_type, + SND_JACK_HEADSET | + SND_JACK_BTN_0 | SND_JACK_BTN_1 | + SND_JACK_BTN_2 | SND_JACK_BTN_3); + + mod_delayed_work(system_power_efficient_wq, + &rt700->jack_btn_check_work, msecs_to_jiffies(200)); + } + + return; + +io_error: + pr_err_ratelimited("IO error in %s, ret %d\n", __func__, ret); +} + +static void rt700_jack_init(struct rt700_priv *rt700) +{ + struct snd_soc_dapm_context *dapm = + snd_soc_component_get_dapm(rt700->component); + + /* power on */ + if (dapm->bias_level <= SND_SOC_BIAS_STANDBY) + regmap_write(rt700->regmap, + RT700_SET_AUDIO_POWER_STATE, AC_PWRST_D0); + + if (rt700->hs_jack) { + /* Enable Jack Detection */ + regmap_write(rt700->regmap, + RT700_SET_MIC2_UNSOLICITED_ENABLE, 0x82); + regmap_write(rt700->regmap, + RT700_SET_HP_UNSOLICITED_ENABLE, 0x81); + regmap_write(rt700->regmap, + RT700_SET_INLINE_UNSOLICITED_ENABLE, 0x83); + rt700_index_write(rt700->regmap, 0x10, 0x2420); + rt700_index_write(rt700->regmap, 0x19, 0x2e11); + + dev_dbg(&rt700->slave->dev, "in %s enable\n", __func__); + + mod_delayed_work(system_power_efficient_wq, + &rt700->jack_detect_work, msecs_to_jiffies(250)); + } else { + regmap_write(rt700->regmap, + RT700_SET_MIC2_UNSOLICITED_ENABLE, 0x00); + regmap_write(rt700->regmap, + RT700_SET_HP_UNSOLICITED_ENABLE, 0x00); + regmap_write(rt700->regmap, + RT700_SET_INLINE_UNSOLICITED_ENABLE, 0x00); + + dev_dbg(&rt700->slave->dev, "in %s disable\n", __func__); + } + + /* power off */ + if (dapm->bias_level <= SND_SOC_BIAS_STANDBY) + regmap_write(rt700->regmap, + RT700_SET_AUDIO_POWER_STATE, AC_PWRST_D3); +} + +static int rt700_set_jack_detect(struct snd_soc_component *component, + struct snd_soc_jack *hs_jack, void *data) +{ + struct rt700_priv *rt700 = snd_soc_component_get_drvdata(component); + + rt700->hs_jack = hs_jack; + + if (!rt700->hw_init) { + dev_dbg(&rt700->slave->dev, + "%s hw_init not ready yet\n", __func__); + return 0; + } + + rt700_jack_init(rt700); + + return 0; +} + +static void rt700_get_gain(struct rt700_priv *rt700, unsigned int addr_h, + unsigned int addr_l, unsigned int val_h, + unsigned int *r_val, unsigned int *l_val) +{ + /* R Channel */ + *r_val = (val_h << 8); + regmap_read(rt700->regmap, addr_l, r_val); + + /* L Channel */ + val_h |= 0x20; + *l_val = (val_h << 8); + regmap_read(rt700->regmap, addr_h, l_val); +} + +/* For Verb-Set Amplifier Gain (Verb ID = 3h) */ +static int rt700_set_amp_gain_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct snd_soc_dapm_context *dapm = + snd_soc_component_get_dapm(component); + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + struct rt700_priv *rt700 = snd_soc_component_get_drvdata(component); + unsigned int addr_h, addr_l, val_h, val_ll, val_lr; + unsigned int read_ll, read_rl; + int i; + + /* Can't use update bit function, so read the original value first */ + addr_h = mc->reg; + addr_l = mc->rreg; + if (mc->shift == RT700_DIR_OUT_SFT) /* output */ + val_h = 0x80; + else /* input */ + val_h = 0x0; + + rt700_get_gain(rt700, addr_h, addr_l, val_h, &read_rl, &read_ll); + + /* L Channel */ + if (mc->invert) { + /* for mute */ + val_ll = (mc->max - ucontrol->value.integer.value[0]) << 7; + /* keep gain */ + read_ll = read_ll & 0x7f; + val_ll |= read_ll; + } else { + /* for gain */ + val_ll = ((ucontrol->value.integer.value[0]) & 0x7f); + if (val_ll > mc->max) + val_ll = mc->max; + /* keep mute status */ + read_ll = read_ll & 0x80; + val_ll |= read_ll; + } + + if (dapm->bias_level <= SND_SOC_BIAS_STANDBY) + regmap_write(rt700->regmap, + RT700_SET_AUDIO_POWER_STATE, AC_PWRST_D0); + + /* R Channel */ + if (mc->invert) { + /* for mute */ + val_lr = (mc->max - ucontrol->value.integer.value[1]) << 7; + /* keep gain */ + read_rl = read_rl & 0x7f; + val_lr |= read_rl; + } else { + /* for gain */ + val_lr = ((ucontrol->value.integer.value[1]) & 0x7f); + if (val_lr > mc->max) + val_lr = mc->max; + /* keep mute status */ + read_rl = read_rl & 0x80; + val_lr |= read_rl; + } + + for (i = 0; i < 3; i++) { /* retry 3 times at most */ + if (val_ll == val_lr) { + /* Set both L/R channels at the same time */ + val_h = (1 << mc->shift) | (3 << 4); + regmap_write(rt700->regmap, + addr_h, (val_h << 8 | val_ll)); + regmap_write(rt700->regmap, + addr_l, (val_h << 8 | val_ll)); + } else { + /* Lch*/ + val_h = (1 << mc->shift) | (1 << 5); + regmap_write(rt700->regmap, + addr_h, (val_h << 8 | val_ll)); + + /* Rch */ + val_h = (1 << mc->shift) | (1 << 4); + regmap_write(rt700->regmap, + addr_l, (val_h << 8 | val_lr)); + } + /* check result */ + if (mc->shift == RT700_DIR_OUT_SFT) /* output */ + val_h = 0x80; + else /* input */ + val_h = 0x0; + + rt700_get_gain(rt700, addr_h, addr_l, val_h, + &read_rl, &read_ll); + if (read_rl == val_lr && read_ll == val_ll) + break; + } + + if (dapm->bias_level <= SND_SOC_BIAS_STANDBY) + regmap_write(rt700->regmap, + RT700_SET_AUDIO_POWER_STATE, AC_PWRST_D3); + return 0; +} + +static int rt700_set_amp_gain_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct rt700_priv *rt700 = snd_soc_component_get_drvdata(component); + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + unsigned int addr_h, addr_l, val_h; + unsigned int read_ll, read_rl; + + addr_h = mc->reg; + addr_l = mc->rreg; + if (mc->shift == RT700_DIR_OUT_SFT) /* output */ + val_h = 0x80; + else /* input */ + val_h = 0x0; + + rt700_get_gain(rt700, addr_h, addr_l, val_h, &read_rl, &read_ll); + + if (mc->invert) { + /* for mute status */ + read_ll = !((read_ll & 0x80) >> RT700_MUTE_SFT); + read_rl = !((read_rl & 0x80) >> RT700_MUTE_SFT); + } else { + /* for gain */ + read_ll = read_ll & 0x7f; + read_rl = read_rl & 0x7f; + } + ucontrol->value.integer.value[0] = read_ll; + ucontrol->value.integer.value[1] = read_rl; + + return 0; +} + +static const DECLARE_TLV_DB_SCALE(out_vol_tlv, -6525, 75, 0); +static const DECLARE_TLV_DB_SCALE(in_vol_tlv, -1725, 75, 0); +static const DECLARE_TLV_DB_SCALE(mic_vol_tlv, 0, 1000, 0); + +static const struct snd_kcontrol_new rt700_snd_controls[] = { + SOC_DOUBLE_R_EXT_TLV("DAC Front Playback Volume", + RT700_SET_GAIN_DAC1_H, RT700_SET_GAIN_DAC1_L, + RT700_DIR_OUT_SFT, 0x57, 0, + rt700_set_amp_gain_get, rt700_set_amp_gain_put, out_vol_tlv), + SOC_DOUBLE_R_EXT("ADC 08 Capture Switch", + RT700_SET_GAIN_ADC2_H, RT700_SET_GAIN_ADC2_L, + RT700_DIR_IN_SFT, 1, 1, + rt700_set_amp_gain_get, rt700_set_amp_gain_put), + SOC_DOUBLE_R_EXT("ADC 09 Capture Switch", + RT700_SET_GAIN_ADC1_H, RT700_SET_GAIN_ADC1_L, + RT700_DIR_IN_SFT, 1, 1, + rt700_set_amp_gain_get, rt700_set_amp_gain_put), + SOC_DOUBLE_R_EXT_TLV("ADC 08 Capture Volume", + RT700_SET_GAIN_ADC2_H, RT700_SET_GAIN_ADC2_L, + RT700_DIR_IN_SFT, 0x3f, 0, + rt700_set_amp_gain_get, rt700_set_amp_gain_put, in_vol_tlv), + SOC_DOUBLE_R_EXT_TLV("ADC 09 Capture Volume", + RT700_SET_GAIN_ADC1_H, RT700_SET_GAIN_ADC1_L, + RT700_DIR_IN_SFT, 0x3f, 0, + rt700_set_amp_gain_get, rt700_set_amp_gain_put, in_vol_tlv), + SOC_DOUBLE_R_EXT_TLV("AMIC Volume", + RT700_SET_GAIN_AMIC_H, RT700_SET_GAIN_AMIC_L, + RT700_DIR_IN_SFT, 3, 0, + rt700_set_amp_gain_get, rt700_set_amp_gain_put, mic_vol_tlv), +}; + +static int rt700_mux_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = + snd_soc_dapm_kcontrol_component(kcontrol); + struct rt700_priv *rt700 = snd_soc_component_get_drvdata(component); + unsigned int reg, val = 0, nid; + int ret; + + if (strstr(ucontrol->id.name, "HPO Mux")) + nid = RT700_HP_OUT; + else if (strstr(ucontrol->id.name, "ADC 22 Mux")) + nid = RT700_MIXER_IN1; + else if (strstr(ucontrol->id.name, "ADC 23 Mux")) + nid = RT700_MIXER_IN2; + else + return -EINVAL; + + /* vid = 0xf01 */ + reg = RT700_VERB_SET_CONNECT_SEL | nid; + ret = regmap_read(rt700->regmap, reg, &val); + if (ret < 0) + return ret; + + ucontrol->value.enumerated.item[0] = val; + + return 0; +} + +static int rt700_mux_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = + snd_soc_dapm_kcontrol_component(kcontrol); + struct snd_soc_dapm_context *dapm = + snd_soc_dapm_kcontrol_dapm(kcontrol); + struct rt700_priv *rt700 = snd_soc_component_get_drvdata(component); + struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; + unsigned int *item = ucontrol->value.enumerated.item; + unsigned int val, val2 = 0, change, reg, nid; + int ret; + + if (item[0] >= e->items) + return -EINVAL; + + if (strstr(ucontrol->id.name, "HPO Mux")) + nid = RT700_HP_OUT; + else if (strstr(ucontrol->id.name, "ADC 22 Mux")) + nid = RT700_MIXER_IN1; + else if (strstr(ucontrol->id.name, "ADC 23 Mux")) + nid = RT700_MIXER_IN2; + else + return -EINVAL; + + /* Verb ID = 0x701h */ + val = snd_soc_enum_item_to_val(e, item[0]) << e->shift_l; + + reg = RT700_VERB_SET_CONNECT_SEL | nid; + ret = regmap_read(rt700->regmap, reg, &val2); + if (ret < 0) + return ret; + + if (val == val2) + change = 0; + else + change = 1; + + if (change) { + reg = RT700_VERB_SET_CONNECT_SEL | nid; + regmap_write(rt700->regmap, reg, val); + } + + snd_soc_dapm_mux_update_power(dapm, kcontrol, + item[0], e, NULL); + + return change; +} + +static const char * const adc_mux_text[] = { + "MIC2", + "LINE1", + "LINE2", + "DMIC", +}; + +static SOC_ENUM_SINGLE_DECL( + rt700_adc22_enum, SND_SOC_NOPM, 0, adc_mux_text); + +static SOC_ENUM_SINGLE_DECL( + rt700_adc23_enum, SND_SOC_NOPM, 0, adc_mux_text); + +static const struct snd_kcontrol_new rt700_adc22_mux = + SOC_DAPM_ENUM_EXT("ADC 22 Mux", rt700_adc22_enum, + rt700_mux_get, rt700_mux_put); + +static const struct snd_kcontrol_new rt700_adc23_mux = + SOC_DAPM_ENUM_EXT("ADC 23 Mux", rt700_adc23_enum, + rt700_mux_get, rt700_mux_put); + +static const char * const out_mux_text[] = { + "Front", + "Surround", +}; + +static SOC_ENUM_SINGLE_DECL( + rt700_hp_enum, SND_SOC_NOPM, 0, out_mux_text); + +static const struct snd_kcontrol_new rt700_hp_mux = + SOC_DAPM_ENUM_EXT("HP Mux", rt700_hp_enum, + rt700_mux_get, rt700_mux_put); + +static int rt700_dac_front_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = + snd_soc_dapm_to_component(w->dapm); + struct rt700_priv *rt700 = snd_soc_component_get_drvdata(component); + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + regmap_write(rt700->regmap, + RT700_SET_STREAMID_DAC1, 0x10); + break; + case SND_SOC_DAPM_PRE_PMD: + regmap_write(rt700->regmap, + RT700_SET_STREAMID_DAC1, 0x00); + break; + } + return 0; +} + +static int rt700_dac_surround_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = + snd_soc_dapm_to_component(w->dapm); + struct rt700_priv *rt700 = snd_soc_component_get_drvdata(component); + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + regmap_write(rt700->regmap, + RT700_SET_STREAMID_DAC2, 0x10); + break; + case SND_SOC_DAPM_PRE_PMD: + regmap_write(rt700->regmap, + RT700_SET_STREAMID_DAC2, 0x00); + break; + } + return 0; +} + +static int rt700_adc_09_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = + snd_soc_dapm_to_component(w->dapm); + struct rt700_priv *rt700 = snd_soc_component_get_drvdata(component); + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + regmap_write(rt700->regmap, + RT700_SET_STREAMID_ADC1, 0x10); + break; + case SND_SOC_DAPM_PRE_PMD: + regmap_write(rt700->regmap, + RT700_SET_STREAMID_ADC1, 0x00); + break; + } + return 0; +} + +static int rt700_adc_08_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = + snd_soc_dapm_to_component(w->dapm); + struct rt700_priv *rt700 = snd_soc_component_get_drvdata(component); + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + regmap_write(rt700->regmap, + RT700_SET_STREAMID_ADC2, 0x10); + break; + case SND_SOC_DAPM_PRE_PMD: + regmap_write(rt700->regmap, + RT700_SET_STREAMID_ADC2, 0x00); + break; + } + return 0; +} + +static int rt700_hpo_mux_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = + snd_soc_dapm_to_component(w->dapm); + struct rt700_priv *rt700 = snd_soc_component_get_drvdata(component); + unsigned int val_h = (1 << RT700_DIR_OUT_SFT) | (0x3 << 4); + unsigned int val_l; + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + val_l = 0x00; + regmap_write(rt700->regmap, + RT700_SET_GAIN_HP_H, (val_h << 8 | val_l)); + break; + case SND_SOC_DAPM_PRE_PMD: + val_l = (1 << RT700_MUTE_SFT); + regmap_write(rt700->regmap, + RT700_SET_GAIN_HP_H, (val_h << 8 | val_l)); + usleep_range(50000, 55000); + break; + } + return 0; +} + +static int rt700_spk_pga_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = + snd_soc_dapm_to_component(w->dapm); + struct rt700_priv *rt700 = snd_soc_component_get_drvdata(component); + unsigned int val_h = (1 << RT700_DIR_OUT_SFT) | (0x3 << 4); + unsigned int val_l; + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + val_l = 0x00; + regmap_write(rt700->regmap, + RT700_SET_GAIN_SPK_H, (val_h << 8 | val_l)); + break; + case SND_SOC_DAPM_PRE_PMD: + val_l = (1 << RT700_MUTE_SFT); + regmap_write(rt700->regmap, + RT700_SET_GAIN_SPK_H, (val_h << 8 | val_l)); + break; + } + return 0; +} + +static const struct snd_soc_dapm_widget rt700_dapm_widgets[] = { + SND_SOC_DAPM_OUTPUT("HP"), + SND_SOC_DAPM_OUTPUT("SPK"), + SND_SOC_DAPM_INPUT("DMIC1"), + SND_SOC_DAPM_INPUT("DMIC2"), + SND_SOC_DAPM_INPUT("MIC2"), + SND_SOC_DAPM_INPUT("LINE1"), + SND_SOC_DAPM_INPUT("LINE2"), + SND_SOC_DAPM_DAC_E("DAC Front", NULL, SND_SOC_NOPM, 0, 0, + rt700_dac_front_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), + SND_SOC_DAPM_DAC_E("DAC Surround", NULL, SND_SOC_NOPM, 0, 0, + rt700_dac_surround_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), + SND_SOC_DAPM_MUX_E("HPO Mux", SND_SOC_NOPM, 0, 0, &rt700_hp_mux, + rt700_hpo_mux_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), + SND_SOC_DAPM_PGA_E("SPK PGA", SND_SOC_NOPM, 0, 0, NULL, 0, + rt700_spk_pga_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), + SND_SOC_DAPM_ADC_E("ADC 09", NULL, SND_SOC_NOPM, 0, 0, + rt700_adc_09_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), + SND_SOC_DAPM_ADC_E("ADC 08", NULL, SND_SOC_NOPM, 0, 0, + rt700_adc_08_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), + SND_SOC_DAPM_MUX("ADC 22 Mux", SND_SOC_NOPM, 0, 0, + &rt700_adc22_mux), + SND_SOC_DAPM_MUX("ADC 23 Mux", SND_SOC_NOPM, 0, 0, + &rt700_adc23_mux), + SND_SOC_DAPM_AIF_IN("DP1RX", "DP1 Playback", 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("DP3RX", "DP3 Playback", 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("DP2TX", "DP2 Capture", 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("DP4TX", "DP4 Capture", 0, SND_SOC_NOPM, 0, 0), +}; + +static const struct snd_soc_dapm_route rt700_audio_map[] = { + {"DAC Front", NULL, "DP1RX"}, + {"DAC Surround", NULL, "DP3RX"}, + {"DP2TX", NULL, "ADC 09"}, + {"DP4TX", NULL, "ADC 08"}, + {"ADC 09", NULL, "ADC 22 Mux"}, + {"ADC 08", NULL, "ADC 23 Mux"}, + {"ADC 22 Mux", "DMIC", "DMIC1"}, + {"ADC 22 Mux", "LINE1", "LINE1"}, + {"ADC 22 Mux", "LINE2", "LINE2"}, + {"ADC 22 Mux", "MIC2", "MIC2"}, + {"ADC 23 Mux", "DMIC", "DMIC2"}, + {"ADC 23 Mux", "LINE1", "LINE1"}, + {"ADC 23 Mux", "LINE2", "LINE2"}, + {"ADC 23 Mux", "MIC2", "MIC2"}, + {"HPO Mux", "Front", "DAC Front"}, + {"HPO Mux", "Surround", "DAC Surround"}, + {"HP", NULL, "HPO Mux"}, + {"SPK PGA", NULL, "DAC Front"}, + {"SPK", NULL, "SPK PGA"}, +}; + +static int rt700_probe(struct snd_soc_component *component) +{ + struct rt700_priv *rt700 = snd_soc_component_get_drvdata(component); + + rt700->component = component; + + return 0; +} + +static int rt700_set_bias_level(struct snd_soc_component *component, + enum snd_soc_bias_level level) +{ + struct snd_soc_dapm_context *dapm = + snd_soc_component_get_dapm(component); + struct rt700_priv *rt700 = snd_soc_component_get_drvdata(component); + + switch (level) { + case SND_SOC_BIAS_PREPARE: + if (dapm->bias_level == SND_SOC_BIAS_STANDBY) { + regmap_write(rt700->regmap, + RT700_SET_AUDIO_POWER_STATE, + AC_PWRST_D0); + } + break; + + case SND_SOC_BIAS_STANDBY: + regmap_write(rt700->regmap, + RT700_SET_AUDIO_POWER_STATE, + AC_PWRST_D3); + break; + + default: + break; + } + dapm->bias_level = level; + return 0; +} + +static const struct snd_soc_component_driver soc_codec_dev_rt700 = { + .probe = rt700_probe, + .set_bias_level = rt700_set_bias_level, + .controls = rt700_snd_controls, + .num_controls = ARRAY_SIZE(rt700_snd_controls), + .dapm_widgets = rt700_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(rt700_dapm_widgets), + .dapm_routes = rt700_audio_map, + .num_dapm_routes = ARRAY_SIZE(rt700_audio_map), + .set_jack = rt700_set_jack_detect, +}; + +static int rt700_set_sdw_stream(struct snd_soc_dai *dai, void *sdw_stream, + int direction) +{ + struct sdw_stream_data *stream; + + stream = kzalloc(sizeof(*stream), GFP_KERNEL); + if (!stream) + return -ENOMEM; + + stream->sdw_stream = (struct sdw_stream_runtime *)sdw_stream; + + /* Use tx_mask or rx_mask to configure stream tag and set dma_data */ + if (direction == SNDRV_PCM_STREAM_PLAYBACK) + dai->playback_dma_data = stream; + else + dai->capture_dma_data = stream; + + return 0; +} + +static void rt700_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct sdw_stream_data *stream; + + stream = snd_soc_dai_get_dma_data(dai, substream); + snd_soc_dai_set_dma_data(dai, substream, NULL); + kfree(stream); +} + +static int rt700_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct rt700_priv *rt700 = snd_soc_component_get_drvdata(component); + struct sdw_stream_config stream_config; + struct sdw_port_config port_config; + enum sdw_data_direction direction; + struct sdw_stream_data *stream; + int retval, port, num_channels; + unsigned int val = 0; + + dev_dbg(dai->dev, "%s %s", __func__, dai->name); + stream = snd_soc_dai_get_dma_data(dai, substream); + + if (!stream) + return -EINVAL; + + if (!rt700->slave) + return -EINVAL; + + /* SoundWire specific configuration */ + /* This code assumes port 1 for playback and port 2 for capture */ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + direction = SDW_DATA_DIR_RX; + port = 1; + } else { + direction = SDW_DATA_DIR_TX; + port = 2; + } + + switch (dai->id) { + case RT700_AIF1: + break; + case RT700_AIF2: + port += 2; + break; + default: + dev_err(component->dev, "Invalid DAI id %d\n", dai->id); + return -EINVAL; + } + + stream_config.frame_rate = params_rate(params); + stream_config.ch_count = params_channels(params); + stream_config.bps = snd_pcm_format_width(params_format(params)); + stream_config.direction = direction; + + num_channels = params_channels(params); + port_config.ch_mask = (1 << (num_channels)) - 1; + port_config.num = port; + + retval = sdw_stream_add_slave(rt700->slave, &stream_config, + &port_config, 1, stream->sdw_stream); + if (retval) { + dev_err(dai->dev, "Unable to configure port\n"); + return retval; + } + + if (params_channels(params) <= 16) { + /* bit 3:0 Number of Channel */ + val |= (params_channels(params) - 1); + } else { + dev_err(component->dev, "Unsupported channels %d\n", + params_channels(params)); + return -EINVAL; + } + + switch (params_width(params)) { + /* bit 6:4 Bits per Sample */ + case 8: + break; + case 16: + val |= (0x1 << 4); + break; + case 20: + val |= (0x2 << 4); + break; + case 24: + val |= (0x3 << 4); + break; + case 32: + val |= (0x4 << 4); + break; + default: + return -EINVAL; + } + + /* 48Khz */ + regmap_write(rt700->regmap, RT700_DAC_FORMAT_H, val); + regmap_write(rt700->regmap, RT700_ADC_FORMAT_H, val); + + return retval; +} + +static int rt700_pcm_hw_free(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct rt700_priv *rt700 = snd_soc_component_get_drvdata(component); + struct sdw_stream_data *stream = + snd_soc_dai_get_dma_data(dai, substream); + + if (!rt700->slave) + return -EINVAL; + + sdw_stream_remove_slave(rt700->slave, stream->sdw_stream); + return 0; +} + +#define RT700_STEREO_RATES (SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000) +#define RT700_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \ + SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S8) + +static struct snd_soc_dai_ops rt700_ops = { + .hw_params = rt700_pcm_hw_params, + .hw_free = rt700_pcm_hw_free, + .set_sdw_stream = rt700_set_sdw_stream, + .shutdown = rt700_shutdown, +}; + +static struct snd_soc_dai_driver rt700_dai[] = { + { + .name = "rt700-aif1", + .id = RT700_AIF1, + .playback = { + .stream_name = "DP1 Playback", + .channels_min = 1, + .channels_max = 2, + .rates = RT700_STEREO_RATES, + .formats = RT700_FORMATS, + }, + .capture = { + .stream_name = "DP2 Capture", + .channels_min = 1, + .channels_max = 2, + .rates = RT700_STEREO_RATES, + .formats = RT700_FORMATS, + }, + .ops = &rt700_ops, + }, + { + .name = "rt700-aif2", + .id = RT700_AIF2, + .playback = { + .stream_name = "DP3 Playback", + .channels_min = 1, + .channels_max = 2, + .rates = RT700_STEREO_RATES, + .formats = RT700_FORMATS, + }, + .capture = { + .stream_name = "DP4 Capture", + .channels_min = 1, + .channels_max = 2, + .rates = RT700_STEREO_RATES, + .formats = RT700_FORMATS, + }, + .ops = &rt700_ops, + }, +}; + +/* Bus clock frequency */ +#define RT700_CLK_FREQ_9600000HZ 9600000 +#define RT700_CLK_FREQ_12000000HZ 12000000 +#define RT700_CLK_FREQ_6000000HZ 6000000 +#define RT700_CLK_FREQ_4800000HZ 4800000 +#define RT700_CLK_FREQ_2400000HZ 2400000 +#define RT700_CLK_FREQ_12288000HZ 12288000 + +int rt700_clock_config(struct device *dev) +{ + struct rt700_priv *rt700 = dev_get_drvdata(dev); + unsigned int clk_freq, value; + + clk_freq = (rt700->params.curr_dr_freq >> 1); + + switch (clk_freq) { + case RT700_CLK_FREQ_12000000HZ: + value = 0x0; + break; + case RT700_CLK_FREQ_6000000HZ: + value = 0x1; + break; + case RT700_CLK_FREQ_9600000HZ: + value = 0x2; + break; + case RT700_CLK_FREQ_4800000HZ: + value = 0x3; + break; + case RT700_CLK_FREQ_2400000HZ: + value = 0x4; + break; + case RT700_CLK_FREQ_12288000HZ: + value = 0x5; + break; + default: + return -EINVAL; + } + + regmap_write(rt700->regmap, 0xe0, value); + regmap_write(rt700->regmap, 0xf0, value); + + dev_dbg(dev, "%s complete, clk_freq=%d\n", __func__, clk_freq); + + return 0; +} + +int rt700_init(struct device *dev, struct regmap *sdw_regmap, + struct regmap *regmap, struct sdw_slave *slave) + +{ + struct rt700_priv *rt700; + int ret; + + rt700 = devm_kzalloc(dev, sizeof(*rt700), GFP_KERNEL); + if (!rt700) + return -ENOMEM; + + dev_set_drvdata(dev, rt700); + rt700->slave = slave; + rt700->sdw_regmap = sdw_regmap; + rt700->regmap = regmap; + + /* + * Mark hw_init to false + * HW init will be performed when device reports present + */ + rt700->hw_init = false; + rt700->first_init = false; + + ret = devm_snd_soc_register_component(dev, + &soc_codec_dev_rt700, + rt700_dai, + ARRAY_SIZE(rt700_dai)); + + dev_dbg(&slave->dev, "%s\n", __func__); + + return ret; +} + +int rt700_io_init(struct device *dev, struct sdw_slave *slave) +{ + struct rt700_priv *rt700 = dev_get_drvdata(dev); + + if (rt700->hw_init) + return 0; + + if (rt700->first_init) { + regcache_cache_only(rt700->regmap, false); + regcache_cache_bypass(rt700->regmap, true); + } + + /* + * PM runtime is only enabled when a Slave reports as Attached + */ + if (!rt700->first_init) { + /* set autosuspend parameters */ + pm_runtime_set_autosuspend_delay(&slave->dev, 3000); + pm_runtime_use_autosuspend(&slave->dev); + + /* update count of parent 'active' children */ + pm_runtime_set_active(&slave->dev); + + /* make sure the device does not suspend immediately */ + pm_runtime_mark_last_busy(&slave->dev); + + pm_runtime_enable(&slave->dev); + } + + pm_runtime_get_noresume(&slave->dev); + + /* reset */ + regmap_write(rt700->regmap, 0xff01, 0x0000); + regmap_write(rt700->regmap, 0x7520, 0x001a); + regmap_write(rt700->regmap, 0x7420, 0xc003); + + /* power on */ + regmap_write(rt700->regmap, RT700_SET_AUDIO_POWER_STATE, AC_PWRST_D0); + /* Set Pin Widget */ + regmap_write(rt700->regmap, RT700_SET_PIN_HP, 0x40); + regmap_write(rt700->regmap, RT700_SET_PIN_SPK, 0x40); + regmap_write(rt700->regmap, RT700_SET_EAPD_SPK, RT700_EAPD_HIGH); + regmap_write(rt700->regmap, RT700_SET_PIN_DMIC1, 0x20); + regmap_write(rt700->regmap, RT700_SET_PIN_DMIC2, 0x20); + regmap_write(rt700->regmap, RT700_SET_PIN_MIC2, 0x20); + + /* Set Configuration Default */ + regmap_write(rt700->regmap, 0x4f12, 0x91); + regmap_write(rt700->regmap, 0x4e12, 0xd6); + regmap_write(rt700->regmap, 0x4d12, 0x11); + regmap_write(rt700->regmap, 0x4c12, 0x20); + regmap_write(rt700->regmap, 0x4f13, 0x91); + regmap_write(rt700->regmap, 0x4e13, 0xd6); + regmap_write(rt700->regmap, 0x4d13, 0x11); + regmap_write(rt700->regmap, 0x4c13, 0x21); + + regmap_write(rt700->regmap, 0x4f19, 0x02); + regmap_write(rt700->regmap, 0x4e19, 0xa1); + regmap_write(rt700->regmap, 0x4d19, 0x90); + regmap_write(rt700->regmap, 0x4c19, 0x80); + + /* Enable Line2 */ + regmap_write(rt700->regmap, 0x371b, 0x40); + regmap_write(rt700->regmap, 0x731b, 0xb0); + regmap_write(rt700->regmap, 0x839b, 0x00); + + /* Set index */ + rt700_index_write(rt700->regmap, 0x4a, 0x201b); + rt700_index_write(rt700->regmap, 0x45, 0x5089); + rt700_index_write(rt700->regmap, 0x6b, 0x5064); + rt700_index_write(rt700->regmap, 0x48, 0xd249); + + /* Finish Initial Settings, set power to D3 */ + regmap_write(rt700->regmap, RT700_SET_AUDIO_POWER_STATE, AC_PWRST_D3); + + if (!rt700->first_init) { + INIT_DELAYED_WORK(&rt700->jack_detect_work, + rt700_jack_detect_handler); + INIT_DELAYED_WORK(&rt700->jack_btn_check_work, + rt700_btn_check_handler); + } + + /* + * if set_jack callback occurred early than io_init, + * we set up the jack detection function now + */ + if (rt700->hs_jack) + rt700_jack_init(rt700); + + if (rt700->first_init) + regcache_cache_bypass(rt700->regmap, false); + else + rt700->first_init = true; + + /* Mark Slave initialization complete */ + rt700->hw_init = true; + + pm_runtime_mark_last_busy(&slave->dev); + pm_runtime_put_autosuspend(&slave->dev); + + dev_dbg(&slave->dev, "%s hw_init complete\n", __func__); + + return 0; +} + +MODULE_DESCRIPTION("ASoC RT700 driver SDW"); +MODULE_AUTHOR("Shuming Fan "); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/codecs/rt700.h b/sound/soc/codecs/rt700.h new file mode 100644 index 00000000000000..11f046e6255e29 --- /dev/null +++ b/sound/soc/codecs/rt700.h @@ -0,0 +1,176 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * rt700.h -- RT700 ALSA SoC audio driver header + * + * Copyright(c) 2019 Realtek Semiconductor Corp. + */ + +#ifndef __RT700_H__ +#define __RT700_H__ + +#include + +extern const struct dev_pm_ops rt700_runtime_pm; + +struct rt700_priv { + struct snd_soc_component *component; + struct regmap *regmap; + struct regmap *sdw_regmap; + struct sdw_slave *slave; + enum sdw_slave_status status; + struct sdw_bus_params params; + bool hw_init; + bool first_init; + struct snd_soc_jack *hs_jack; + struct delayed_work jack_detect_work; + struct delayed_work jack_btn_check_work; + int jack_type; +}; + +struct sdw_stream_data { + struct sdw_stream_runtime *sdw_stream; +}; + +/* NID */ +#define RT700_AUDIO_FUNCTION_GROUP 0x01 +#define RT700_DAC_OUT1 0x02 +#define RT700_DAC_OUT2 0x03 +#define RT700_ADC_IN1 0x09 +#define RT700_ADC_IN2 0x08 +#define RT700_DMIC1 0x12 +#define RT700_DMIC2 0x13 +#define RT700_SPK_OUT 0x14 +#define RT700_MIC2 0x19 +#define RT700_LINE1 0x1a +#define RT700_LINE2 0x1b +#define RT700_BEEP 0x1d +#define RT700_SPDIF 0x1e +#define RT700_VENDOR_REGISTERS 0x20 +#define RT700_HP_OUT 0x21 +#define RT700_MIXER_IN1 0x22 +#define RT700_MIXER_IN2 0x23 +#define RT700_INLINE_CMD 0x55 + +/* Index (NID:20h) */ +#define RT700_DAC_DC_CALI_CTL1 0x00 +#define RT700_PARA_VERB_CTL 0x1a +#define RT700_COMBO_JACK_AUTO_CTL1 0x45 +#define RT700_COMBO_JACK_AUTO_CTL2 0x46 +#define RT700_INLINE_CMD_CTL 0x48 +#define RT700_DIGITAL_MISC_CTRL4 0x4a +#define RT700_VREFOUT_CTL 0x6b +#define RT700_FSM_CTL 0x6f +#define RT700_IRQ_FLAG_TABLE1 0x80 +#define RT700_IRQ_FLAG_TABLE2 0x81 +#define RT700_IRQ_FLAG_TABLE3 0x82 + +/* Verb */ +#define RT700_VERB_SET_CONNECT_SEL 0x3100 +#define RT700_VERB_SET_EAPD_BTLENABLE 0x3c00 +#define RT700_VERB_GET_CONNECT_SEL 0xb100 +#define RT700_VERB_SET_POWER_STATE 0x3500 +#define RT700_VERB_SET_CHANNEL_STREAMID 0x3600 +#define RT700_VERB_SET_PIN_WIDGET_CONTROL 0x3700 +#define RT700_VERB_SET_UNSOLICITED_ENABLE 0x3800 +#define RT700_SET_AMP_GAIN_MUTE_H 0x7300 +#define RT700_SET_AMP_GAIN_MUTE_L 0x8380 +#define RT700_VERB_GET_PIN_SENSE 0xb900 + +#define RT700_READ_HDA_3 0x2012 +#define RT700_READ_HDA_2 0x2013 +#define RT700_READ_HDA_1 0x2014 +#define RT700_READ_HDA_0 0x2015 +#define RT700_PRIV_INDEX_W_H 0x7520 +#define RT700_PRIV_INDEX_W_L 0x85a0 +#define RT700_PRIV_DATA_W_H 0x7420 +#define RT700_PRIV_DATA_W_L 0x84a0 +#define RT700_PRIV_INDEX_R_H 0x9d20 +#define RT700_PRIV_INDEX_R_L 0xada0 +#define RT700_PRIV_DATA_R_H 0x9c20 +#define RT700_PRIV_DATA_R_L 0xaca0 +#define RT700_DAC_FORMAT_H 0x7203 +#define RT700_DAC_FORMAT_L 0x8283 +#define RT700_ADC_FORMAT_H 0x7209 +#define RT700_ADC_FORMAT_L 0x8289 +#define RT700_SET_AUDIO_POWER_STATE\ + (RT700_VERB_SET_POWER_STATE | RT700_AUDIO_FUNCTION_GROUP) +#define RT700_SET_PIN_DMIC1\ + (RT700_VERB_SET_PIN_WIDGET_CONTROL | RT700_DMIC1) +#define RT700_SET_PIN_DMIC2\ + (RT700_VERB_SET_PIN_WIDGET_CONTROL | RT700_DMIC2) +#define RT700_SET_PIN_SPK\ + (RT700_VERB_SET_PIN_WIDGET_CONTROL | RT700_SPK_OUT) +#define RT700_SET_PIN_HP\ + (RT700_VERB_SET_PIN_WIDGET_CONTROL | RT700_HP_OUT) +#define RT700_SET_PIN_MIC2\ + (RT700_VERB_SET_PIN_WIDGET_CONTROL | RT700_MIC2) +#define RT700_SET_PIN_LINE1\ + (RT700_VERB_SET_PIN_WIDGET_CONTROL | RT700_LINE1) +#define RT700_SET_PIN_LINE2\ + (RT700_VERB_SET_PIN_WIDGET_CONTROL | RT700_LINE2) +#define RT700_SET_MIC2_UNSOLICITED_ENABLE\ + (RT700_VERB_SET_UNSOLICITED_ENABLE | RT700_MIC2) +#define RT700_SET_HP_UNSOLICITED_ENABLE\ + (RT700_VERB_SET_UNSOLICITED_ENABLE | RT700_HP_OUT) +#define RT700_SET_INLINE_UNSOLICITED_ENABLE\ + (RT700_VERB_SET_UNSOLICITED_ENABLE | RT700_INLINE_CMD) +#define RT700_SET_STREAMID_DAC1\ + (RT700_VERB_SET_CHANNEL_STREAMID | RT700_DAC_OUT1) +#define RT700_SET_STREAMID_DAC2\ + (RT700_VERB_SET_CHANNEL_STREAMID | RT700_DAC_OUT2) +#define RT700_SET_STREAMID_ADC1\ + (RT700_VERB_SET_CHANNEL_STREAMID | RT700_ADC_IN1) +#define RT700_SET_STREAMID_ADC2\ + (RT700_VERB_SET_CHANNEL_STREAMID | RT700_ADC_IN2) +#define RT700_SET_GAIN_DAC1_L\ + (RT700_SET_AMP_GAIN_MUTE_L | RT700_DAC_OUT1) +#define RT700_SET_GAIN_DAC1_H\ + (RT700_SET_AMP_GAIN_MUTE_H | RT700_DAC_OUT1) +#define RT700_SET_GAIN_ADC1_L\ + (RT700_SET_AMP_GAIN_MUTE_L | RT700_ADC_IN1) +#define RT700_SET_GAIN_ADC1_H\ + (RT700_SET_AMP_GAIN_MUTE_H | RT700_ADC_IN1) +#define RT700_SET_GAIN_ADC2_L\ + (RT700_SET_AMP_GAIN_MUTE_L | RT700_ADC_IN2) +#define RT700_SET_GAIN_ADC2_H\ + (RT700_SET_AMP_GAIN_MUTE_H | RT700_ADC_IN2) +#define RT700_SET_GAIN_AMIC_L\ + (RT700_SET_AMP_GAIN_MUTE_L | RT700_MIC2) +#define RT700_SET_GAIN_AMIC_H\ + (RT700_SET_AMP_GAIN_MUTE_H | RT700_MIC2) +#define RT700_SET_GAIN_HP_L\ + (RT700_SET_AMP_GAIN_MUTE_L | RT700_HP_OUT) +#define RT700_SET_GAIN_HP_H\ + (RT700_SET_AMP_GAIN_MUTE_H | RT700_HP_OUT) +#define RT700_SET_GAIN_SPK_L\ + (RT700_SET_AMP_GAIN_MUTE_L | RT700_SPK_OUT) +#define RT700_SET_GAIN_SPK_H\ + (RT700_SET_AMP_GAIN_MUTE_H | RT700_SPK_OUT) +#define RT700_SET_EAPD_SPK\ + (RT700_VERB_SET_EAPD_BTLENABLE | RT700_SPK_OUT) + +/* combo jack auto switch control 2 (0x46)(NID:20h) */ +#define RT700_COMBOJACK_AUTO_DET_STATUS (0x1 << 11) +#define RT700_COMBOJACK_AUTO_DET_TRS (0x1 << 10) +#define RT700_COMBOJACK_AUTO_DET_CTIA (0x1 << 9) +#define RT700_COMBOJACK_AUTO_DET_OMTP (0x1 << 8) + +#define RT700_EAPD_HIGH 0x2 +#define RT700_EAPD_LOW 0x0 +#define RT700_MUTE_SFT 7 +#define RT700_DIR_IN_SFT 6 +#define RT700_DIR_OUT_SFT 7 + +enum { + RT700_AIF1, + RT700_AIF2, + RT700_AIFS, +}; + +int rt700_io_init(struct device *dev, struct sdw_slave *slave); +int rt700_init(struct device *dev, struct regmap *sdw_regmap, + struct regmap *regmap, struct sdw_slave *slave); + +int rt700_jack_detect(struct rt700_priv *rt700, bool *hp, bool *mic); +int rt700_clock_config(struct device *dev); +#endif /* __RT700_H__ */ diff --git a/sound/soc/codecs/rt711-sdw.c b/sound/soc/codecs/rt711-sdw.c new file mode 100644 index 00000000000000..ee7fa574d3298d --- /dev/null +++ b/sound/soc/codecs/rt711-sdw.c @@ -0,0 +1,553 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// rt711-sdw.c -- rt711 ALSA SoC audio driver +// +// Copyright(c) 2019 Realtek Semiconductor Corp. +// +// + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "rt711.h" +#include "rt711-sdw.h" + +static bool rt711_readable_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case 0x00e0: + case 0x00f0: + case 0x2012 ... 0x2016: + case 0x201a ... 0x2027: + case 0x2029 ... 0x202a: + case 0x202d ... 0x2034: + case 0x2201 ... 0x2204: + case 0x2206 ... 0x2212: + case 0x2220 ... 0x2223: + case 0x2230 ... 0x2239: + case 0x2f01 ... 0x2f0f: + case 0x3000 ... 0x3fff: + case 0x7000 ... 0x7fff: + case 0x8300 ... 0x83ff: + case 0x9c00 ... 0x9cff: + case 0xb900 ... 0xb9ff: + case 0x752009: + case 0x752011: + case 0x75201a: + case 0x752045: + case 0x752046: + case 0x752048: + case 0x75204a: + case 0x75206b: + case 0x75206f: + case 0x752080: + case 0x752081: + case 0x752091: + case 0x755800: + return true; + default: + return false; + } +} + +static bool rt711_volatile_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case 0x2016: + case 0x201b: + case 0x201c: + case 0x201d: + case 0x201f: + case 0x2021: + case 0x2023: + case 0x2230: + case 0x2012 ... 0x2015: /* HD-A read */ + case 0x202d ... 0x202f: /* BRA */ + case 0x2201 ... 0x2212: /* i2c debug */ + case 0x2220 ... 0x2223: /* decoded HD-A */ + case 0x9c00 ... 0x9cff: + case 0xb900 ... 0xb9ff: + case 0xff01: + case 0x75201a: + case 0x752046: + case 0x752080: + case 0x752081: + case 0x755800: + return true; + default: + return false; + } +} + +static int rt711_sdw_read(void *context, unsigned int reg, unsigned int *val) +{ + struct device *dev = context; + struct rt711_priv *rt711 = dev_get_drvdata(dev); + unsigned int sdw_data_3, sdw_data_2, sdw_data_1, sdw_data_0; + unsigned int reg2 = 0, reg3 = 0, reg4 = 0, mask, nid, val2; + unsigned int is_hda_reg = 1, is_index_reg = 0; + int ret; + + if (reg > 0xffff) + is_index_reg = 1; + + mask = reg & 0xf000; + + if (is_index_reg) { /* index registers */ + val2 = reg & 0xff; + reg = reg >> 8; + nid = reg & 0xff; + ret = regmap_write(rt711->sdw_regmap, reg, 0); + if (ret < 0) + return ret; + reg2 = reg + 0x1000; + reg2 |= 0x80; + ret = regmap_write(rt711->sdw_regmap, reg2, val2); + if (ret < 0) + return ret; + + reg3 = RT711_PRIV_DATA_R_H | nid; + ret = regmap_write(rt711->sdw_regmap, + reg3, ((*val >> 8) & 0xff)); + if (ret < 0) + return ret; + reg4 = reg3 + 0x1000; + reg4 |= 0x80; + ret = regmap_write(rt711->sdw_regmap, reg4, (*val & 0xff)); + if (ret < 0) + return ret; + } else if (mask == 0x3000) { + reg += 0x8000; + ret = regmap_write(rt711->sdw_regmap, reg, *val); + if (ret < 0) + return ret; + } else if (mask == 0x7000) { + reg += 0x2000; + reg |= 0x800; + ret = regmap_write(rt711->sdw_regmap, + reg, ((*val >> 8) & 0xff)); + if (ret < 0) + return ret; + reg2 = reg + 0x1000; + reg2 |= 0x80; + ret = regmap_write(rt711->sdw_regmap, reg2, (*val & 0xff)); + if (ret < 0) + return ret; + } else if ((reg & 0xff00) == 0x8300) { /* for R channel */ + reg2 = reg - 0x1000; + reg2 &= ~0x80; + ret = regmap_write(rt711->sdw_regmap, + reg2, ((*val >> 8) & 0xff)); + if (ret < 0) + return ret; + ret = regmap_write(rt711->sdw_regmap, reg, (*val & 0xff)); + if (ret < 0) + return ret; + } else if (mask == 0x9000) { + ret = regmap_write(rt711->sdw_regmap, + reg, ((*val >> 8) & 0xff)); + if (ret < 0) + return ret; + reg2 = reg + 0x1000; + reg2 |= 0x80; + ret = regmap_write(rt711->sdw_regmap, reg2, (*val & 0xff)); + if (ret < 0) + return ret; + } else if (mask == 0xb000) { + ret = regmap_write(rt711->sdw_regmap, reg, *val); + if (ret < 0) + return ret; + } else { + ret = regmap_read(rt711->sdw_regmap, reg, val); + if (ret < 0) + return ret; + is_hda_reg = 0; + } + + if (is_hda_reg || is_index_reg) { + sdw_data_3 = 0; + sdw_data_2 = 0; + sdw_data_1 = 0; + sdw_data_0 = 0; + ret = regmap_read(rt711->sdw_regmap, + RT711_READ_HDA_3, &sdw_data_3); + if (ret < 0) + return ret; + ret = regmap_read(rt711->sdw_regmap, + RT711_READ_HDA_2, &sdw_data_2); + if (ret < 0) + return ret; + ret = regmap_read(rt711->sdw_regmap, + RT711_READ_HDA_1, &sdw_data_1); + if (ret < 0) + return ret; + ret = regmap_read(rt711->sdw_regmap, + RT711_READ_HDA_0, &sdw_data_0); + if (ret < 0) + return ret; + *val = ((sdw_data_3 & 0xff) << 24) | + ((sdw_data_2 & 0xff) << 16) | + ((sdw_data_1 & 0xff) << 8) | (sdw_data_0 & 0xff); + } + + if (is_hda_reg == 0) + dev_dbg(dev, "[%s] %04x => %08x\n", __func__, reg, *val); + else if (is_index_reg) + dev_dbg(dev, "[%s] %04x %04x %04x %04x => %08x\n", + __func__, reg, reg2, reg3, reg4, *val); + else + dev_dbg(dev, "[%s] %04x %04x => %08x\n", + __func__, reg, reg2, *val); + + return 0; +} + +static int rt711_sdw_write(void *context, unsigned int reg, unsigned int val) +{ + struct device *dev = context; + struct rt711_priv *rt711 = dev_get_drvdata(dev); + unsigned int reg2 = 0, reg3, reg4, nid, mask, val2; + unsigned int is_index_reg = 0; + int ret; + + if (reg > 0xffff) + is_index_reg = 1; + + mask = reg & 0xf000; + + if (is_index_reg) { /* index registers */ + val2 = reg & 0xff; + reg = reg >> 8; + nid = reg & 0xff; + ret = regmap_write(rt711->sdw_regmap, reg, 0); + if (ret < 0) + return ret; + reg2 = reg + 0x1000; + reg2 |= 0x80; + ret = regmap_write(rt711->sdw_regmap, reg2, val2); + if (ret < 0) + return ret; + + reg3 = RT711_PRIV_DATA_W_H | nid; + ret = regmap_write(rt711->sdw_regmap, + reg3, ((val >> 8) & 0xff)); + if (ret < 0) + return ret; + reg4 = reg3 + 0x1000; + reg4 |= 0x80; + ret = regmap_write(rt711->sdw_regmap, reg4, (val & 0xff)); + if (ret < 0) + return ret; + is_index_reg = 1; + } else if (reg < 0x4fff) { + ret = regmap_write(rt711->sdw_regmap, reg, val); + if (ret < 0) + return ret; + } else if (reg == RT711_FUNC_RESET) { + ret = regmap_write(rt711->sdw_regmap, reg, val); + if (ret < 0) + return ret; + } else if (mask == 0x7000) { + ret = regmap_write(rt711->sdw_regmap, + reg, ((val >> 8) & 0xff)); + if (ret < 0) + return ret; + reg2 = reg + 0x1000; + reg2 |= 0x80; + ret = regmap_write(rt711->sdw_regmap, reg2, (val & 0xff)); + if (ret < 0) + return ret; + } else if ((reg & 0xff00) == 0x8300) { /* for R channel */ + reg2 = reg - 0x1000; + reg2 &= ~0x80; + ret = regmap_write(rt711->sdw_regmap, + reg2, ((val >> 8) & 0xff)); + if (ret < 0) + return ret; + ret = regmap_write(rt711->sdw_regmap, reg, (val & 0xff)); + if (ret < 0) + return ret; + } + + if (reg2 == 0) + dev_dbg(dev, "[%s] %04x <= %04x\n", __func__, reg, val); + else if (is_index_reg) + dev_dbg(dev, "[%s] %04x %04x %04x %04x <= %04x %04x\n", + __func__, reg, reg2, reg3, reg4, val2, val); + else + dev_dbg(dev, "[%s] %04x %04x <= %04x\n", + __func__, reg, reg2, val); + + return 0; +} + +static const struct regmap_config rt711_regmap = { + .reg_bits = 24, + .val_bits = 32, + .readable_reg = rt711_readable_register, /* Readable registers */ + .volatile_reg = rt711_volatile_register, /* volatile register */ + .max_register = 0x755800, /* Maximum number of register */ + .reg_defaults = rt711_reg_defaults, /* Defaults */ + .num_reg_defaults = ARRAY_SIZE(rt711_reg_defaults), + .cache_type = REGCACHE_RBTREE, + .use_single_read = true, + .use_single_write = true, + .reg_read = rt711_sdw_read, + .reg_write = rt711_sdw_write, +}; + +static const struct regmap_config rt711_sdw_regmap = { + .name = "sdw", + .reg_bits = 32, /* Total register space for SDW */ + .val_bits = 8, /* Total number of bits in register */ + .readable_reg = rt711_readable_register, /* Readable registers */ + .max_register = 0xff01, /* Maximum number of register */ + .cache_type = REGCACHE_NONE, + .use_single_read = true, + .use_single_write = true, +}; + +static int rt711_update_status(struct sdw_slave *slave, + enum sdw_slave_status status) +{ + struct rt711_priv *rt711 = dev_get_drvdata(&slave->dev); + + /* Update the status */ + rt711->status = status; + + if (status == SDW_SLAVE_UNATTACHED) + rt711->hw_init = false; + + /* + * Perform initialization only if slave status is present and + * hw_init flag is false + */ + if (rt711->hw_init || rt711->status != SDW_SLAVE_ATTACHED) + return 0; + + /* perform I/O transfers required for Slave initialization */ + return rt711_io_init(&slave->dev, slave); +} + +static int rt711_read_prop(struct sdw_slave *slave) +{ + struct sdw_slave_prop *prop = &slave->prop; + int nval, i, num_of_ports = 1; + u32 bit; + unsigned long addr; + struct sdw_dpn_prop *dpn; + + prop->paging_support = false; + + /* first we need to allocate memory for set bits in port lists */ + prop->source_ports = 0x14; /* BITMAP: 00010100 */ + prop->sink_ports = 0x8; /* BITMAP: 00001000 */ + + nval = hweight32(prop->source_ports); + num_of_ports += nval; + prop->src_dpn_prop = devm_kcalloc(&slave->dev, nval, + sizeof(*prop->src_dpn_prop), + GFP_KERNEL); + if (!prop->src_dpn_prop) + return -ENOMEM; + + i = 0; + dpn = prop->src_dpn_prop; + addr = prop->source_ports; + for_each_set_bit(bit, &addr, 32) { + dpn[i].num = bit; + dpn[i].type = SDW_DPN_FULL; + dpn[i].simple_ch_prep_sm = true; + dpn[i].ch_prep_timeout = 10; + i++; + } + + /* do this again for sink now */ + nval = hweight32(prop->sink_ports); + num_of_ports += nval; + prop->sink_dpn_prop = devm_kcalloc(&slave->dev, nval, + sizeof(*prop->sink_dpn_prop), + GFP_KERNEL); + if (!prop->sink_dpn_prop) + return -ENOMEM; + + i = 0; + dpn = prop->sink_dpn_prop; + addr = prop->sink_ports; + for_each_set_bit(bit, &addr, 32) { + dpn[i].num = bit; + dpn[i].type = SDW_DPN_FULL; + dpn[i].simple_ch_prep_sm = true; + dpn[i].ch_prep_timeout = 10; + i++; + } + + /* Allocate port_ready based on num_of_ports */ + slave->port_ready = devm_kcalloc(&slave->dev, num_of_ports, + sizeof(*slave->port_ready), + GFP_KERNEL); + if (!slave->port_ready) + return -ENOMEM; + + /* Initialize completion */ + for (i = 0; i < num_of_ports; i++) + init_completion(&slave->port_ready[i]); + + /* set the timeout values */ + prop->clk_stop_timeout = 20; + + /* wake-up event */ + prop->wake_capable = 1; + + return 0; +} + +static int rt711_bus_config(struct sdw_slave *slave, + struct sdw_bus_params *params) +{ + struct rt711_priv *rt711 = dev_get_drvdata(&slave->dev); + int ret; + + memcpy(&rt711->params, params, sizeof(*params)); + + ret = rt711_clock_config(&slave->dev); + if (ret < 0) + dev_err(&slave->dev, "Invalid clk config"); + + return ret; +} + +static int rt711_interrupt_callback(struct sdw_slave *slave, + struct sdw_slave_intr_status *status) +{ + struct rt711_priv *rt711 = dev_get_drvdata(&slave->dev); + + dev_dbg(&slave->dev, + "%s control_port_stat=%x", __func__, status->control_port); + + if (status->control_port & 0x4) { + mod_delayed_work(system_power_efficient_wq, + &rt711->jack_detect_work, msecs_to_jiffies(250)); + } + + return 0; +} + +static struct sdw_slave_ops rt711_slave_ops = { + .read_prop = rt711_read_prop, + .interrupt_callback = rt711_interrupt_callback, + .update_status = rt711_update_status, + .bus_config = rt711_bus_config, +}; + +static int rt711_sdw_probe(struct sdw_slave *slave, + const struct sdw_device_id *id) +{ + struct regmap *sdw_regmap, *regmap; + + /* Assign ops */ + slave->ops = &rt711_slave_ops; + + /* Regmap Initialization */ + sdw_regmap = devm_regmap_init_sdw(slave, &rt711_sdw_regmap); + if (!sdw_regmap) + return -EINVAL; + + regmap = devm_regmap_init(&slave->dev, NULL, + &slave->dev, &rt711_regmap); + if (!regmap) + return -EINVAL; + + rt711_init(&slave->dev, sdw_regmap, regmap, slave); + + return 0; +} + +static int rt711_sdw_remove(struct sdw_slave *slave) +{ + struct rt711_priv *rt711 = dev_get_drvdata(&slave->dev); + + if (rt711 && rt711->hw_init) { + cancel_delayed_work(&rt711->jack_detect_work); + cancel_delayed_work(&rt711->jack_btn_check_work); + cancel_work_sync(&rt711->calibration_work); + } + + return 0; +} + +static const struct sdw_device_id rt711_id[] = { + SDW_SLAVE_ENTRY(0x025d, 0x711, 0), + {}, +}; +MODULE_DEVICE_TABLE(sdw, rt711_id); + +static int rt711_dev_suspend(struct device *dev) +{ + struct rt711_priv *rt711 = dev_get_drvdata(dev); + + if (!rt711->hw_init) + return 0; + + regcache_cache_only(rt711->regmap, true); + + return 0; +} + +#define RT711_PROBE_TIMEOUT 2000 + +static int rt711_dev_resume(struct device *dev) +{ + struct sdw_slave *slave = to_sdw_slave_device(dev); + struct rt711_priv *rt711 = dev_get_drvdata(dev); + unsigned long time; + + if (!rt711->hw_init) + return 0; + + if (!slave->unattach_request) + goto regmap_sync; + + time = wait_for_completion_timeout(&slave->initialization_complete, + msecs_to_jiffies(RT711_PROBE_TIMEOUT)); + if (!time) { + dev_err(&slave->dev, "Initialization not complete, timed out\n"); + return -ETIMEDOUT; + } + +regmap_sync: + slave->unattach_request = 0; + regcache_cache_only(rt711->regmap, false); + regcache_sync_region(rt711->regmap, 0x3000, 0x8fff); + regcache_sync_region(rt711->regmap, 0x752010, 0x752091); + + return 0; +} + +static const struct dev_pm_ops rt711_pm = { + SET_SYSTEM_SLEEP_PM_OPS(rt711_dev_suspend, rt711_dev_resume) + SET_RUNTIME_PM_OPS(rt711_dev_suspend, rt711_dev_resume, NULL) +}; + +static struct sdw_driver rt711_sdw_driver = { + .driver = { + .name = "rt711", + .owner = THIS_MODULE, + .pm = &rt711_pm, + }, + .probe = rt711_sdw_probe, + .remove = rt711_sdw_remove, + .ops = &rt711_slave_ops, + .id_table = rt711_id, +}; +module_sdw_driver(rt711_sdw_driver); + +MODULE_DESCRIPTION("ASoC RT711 SDW driver"); +MODULE_AUTHOR("Shuming Fan "); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/rt711-sdw.h b/sound/soc/codecs/rt711-sdw.h new file mode 100644 index 00000000000000..43b2b984b29cb9 --- /dev/null +++ b/sound/soc/codecs/rt711-sdw.h @@ -0,0 +1,281 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * rt711-sdw.h -- RT711 ALSA SoC audio driver header + * + * Copyright(c) 2019 Realtek Semiconductor Corp. + */ + +#ifndef __RT711_SDW_H__ +#define __RT711_SDW_H__ + +static const struct reg_default rt711_reg_defaults[] = { + { 0x0000, 0x00 }, + { 0x0001, 0x00 }, + { 0x0002, 0x00 }, + { 0x0003, 0x00 }, + { 0x0004, 0x00 }, + { 0x0005, 0x01 }, + { 0x0020, 0x00 }, + { 0x0022, 0x00 }, + { 0x0023, 0x00 }, + { 0x0024, 0x00 }, + { 0x0025, 0x00 }, + { 0x0026, 0x00 }, + { 0x0030, 0x00 }, + { 0x0032, 0x00 }, + { 0x0033, 0x00 }, + { 0x0034, 0x00 }, + { 0x0035, 0x00 }, + { 0x0036, 0x00 }, + { 0x0040, 0x00 }, + { 0x0041, 0x00 }, + { 0x0042, 0x00 }, + { 0x0043, 0x00 }, + { 0x0044, 0x20 }, + { 0x0045, 0x01 }, + { 0x0046, 0x01 }, + { 0x0050, 0x20 }, + { 0x0051, 0x02 }, + { 0x0052, 0x5d }, + { 0x0053, 0x07 }, + { 0x0054, 0x11 }, + { 0x0055, 0x00 }, + { 0x0060, 0x00 }, + { 0x0070, 0x00 }, + { 0x0080, 0xc0 }, + { 0x0088, 0x00 }, + { 0x00e0, 0x00 }, + { 0x00e1, 0x00 }, + { 0x00e2, 0x00 }, + { 0x00e3, 0x00 }, + { 0x00e5, 0x00 }, + { 0x00ee, 0x00 }, + { 0x00ef, 0x00 }, + { 0x00f0, 0x00 }, + { 0x00f1, 0x00 }, + { 0x00f2, 0x00 }, + { 0x00f3, 0x00 }, + { 0x00f4, 0x00 }, + { 0x00f5, 0x00 }, + { 0x00fe, 0x00 }, + { 0x00ff, 0x00 }, + { 0x0100, 0x00 }, + { 0x0101, 0x00 }, + { 0x0102, 0x00 }, + { 0x0103, 0x00 }, + { 0x0104, 0x00 }, + { 0x0105, 0x00 }, + { 0x0120, 0x00 }, + { 0x0122, 0x00 }, + { 0x0123, 0x00 }, + { 0x0124, 0x00 }, + { 0x0125, 0x00 }, + { 0x0126, 0x00 }, + { 0x0127, 0x00 }, + { 0x0130, 0x00 }, + { 0x0132, 0x00 }, + { 0x0133, 0x00 }, + { 0x0134, 0x00 }, + { 0x0135, 0x00 }, + { 0x0136, 0x00 }, + { 0x0137, 0x00 }, + { 0x0200, 0x00 }, + { 0x0201, 0x00 }, + { 0x0202, 0x00 }, + { 0x0203, 0x00 }, + { 0x0204, 0x00 }, + { 0x0205, 0x03 }, + { 0x0220, 0x00 }, + { 0x0222, 0x00 }, + { 0x0223, 0x00 }, + { 0x0224, 0x00 }, + { 0x0225, 0x00 }, + { 0x0226, 0x00 }, + { 0x0227, 0x00 }, + { 0x0230, 0x00 }, + { 0x0232, 0x00 }, + { 0x0233, 0x00 }, + { 0x0234, 0x00 }, + { 0x0235, 0x00 }, + { 0x0236, 0x00 }, + { 0x0237, 0x00 }, + { 0x0300, 0x00 }, + { 0x0301, 0x00 }, + { 0x0302, 0x20 }, + { 0x0303, 0x00 }, + { 0x0304, 0x00 }, + { 0x0305, 0x03 }, + { 0x0320, 0x00 }, + { 0x0322, 0x00 }, + { 0x0323, 0x00 }, + { 0x0324, 0x00 }, + { 0x0325, 0x00 }, + { 0x0326, 0x00 }, + { 0x0327, 0x00 }, + { 0x0330, 0x00 }, + { 0x0332, 0x00 }, + { 0x0333, 0x00 }, + { 0x0334, 0x00 }, + { 0x0335, 0x00 }, + { 0x0336, 0x00 }, + { 0x0337, 0x00 }, + { 0x0400, 0x00 }, + { 0x0401, 0x00 }, + { 0x0402, 0x00 }, + { 0x0403, 0x00 }, + { 0x0404, 0x00 }, + { 0x0405, 0x03 }, + { 0x0420, 0x00 }, + { 0x0422, 0x00 }, + { 0x0423, 0x00 }, + { 0x0424, 0x00 }, + { 0x0425, 0x00 }, + { 0x0426, 0x00 }, + { 0x0427, 0x00 }, + { 0x0430, 0x00 }, + { 0x0432, 0x00 }, + { 0x0433, 0x00 }, + { 0x0434, 0x00 }, + { 0x0435, 0x00 }, + { 0x0436, 0x00 }, + { 0x0437, 0x00 }, + { 0x0f00, 0x00 }, + { 0x0f01, 0x00 }, + { 0x0f02, 0x20 }, + { 0x0f03, 0x00 }, + { 0x0f04, 0x00 }, + { 0x0f05, 0x03 }, + { 0x0f06, 0x00 }, + { 0x0f07, 0x00 }, + { 0x0f08, 0x00 }, + { 0x0f09, 0x00 }, + { 0x0f10, 0x00 }, + { 0x0f11, 0x00 }, + { 0x0f12, 0x00 }, + { 0x0f13, 0x00 }, + { 0x0f14, 0x00 }, + { 0x0f15, 0x00 }, + { 0x0f16, 0x00 }, + { 0x0f17, 0x00 }, + { 0x0f18, 0x00 }, + { 0x0f19, 0x00 }, + { 0x0f1a, 0x00 }, + { 0x0f1b, 0x00 }, + { 0x0f1c, 0x00 }, + { 0x0f1d, 0x00 }, + { 0x0f1e, 0x00 }, + { 0x0f1f, 0x00 }, + { 0x0f20, 0x00 }, + { 0x0f22, 0x00 }, + { 0x0f23, 0x00 }, + { 0x0f24, 0x00 }, + { 0x0f25, 0x00 }, + { 0x0f26, 0x00 }, + { 0x0f27, 0x00 }, + { 0x0f30, 0x00 }, + { 0x0f32, 0x00 }, + { 0x0f33, 0x00 }, + { 0x0f34, 0x00 }, + { 0x0f35, 0x00 }, + { 0x0f36, 0x00 }, + { 0x0f37, 0x00 }, + { 0x2012, 0x00 }, + { 0x2013, 0x00 }, + { 0x2014, 0x00 }, + { 0x2015, 0x00 }, + { 0x2016, 0x00 }, + { 0x201a, 0x00 }, + { 0x201b, 0x00 }, + { 0x201c, 0x0c }, + { 0x201d, 0x00 }, + { 0x201e, 0x00 }, + { 0x201f, 0x00 }, + { 0x2020, 0x00 }, + { 0x2021, 0x00 }, + { 0x2022, 0x00 }, + { 0x2023, 0x00 }, + { 0x2024, 0x00 }, + { 0x2025, 0x01 }, + { 0x2026, 0x00 }, + { 0x2027, 0x00 }, + { 0x2029, 0x00 }, + { 0x202a, 0x00 }, + { 0x202d, 0x00 }, + { 0x202e, 0x00 }, + { 0x202f, 0x00 }, + { 0x2030, 0x00 }, + { 0x2031, 0x00 }, + { 0x2032, 0x00 }, + { 0x2033, 0x00 }, + { 0x2034, 0x00 }, + { 0x2201, 0xc7 }, + { 0x2202, 0x0c }, + { 0x2203, 0x22 }, + { 0x2204, 0x04 }, + { 0x2206, 0x00 }, + { 0x2207, 0x00 }, + { 0x2208, 0x00 }, + { 0x2209, 0x00 }, + { 0x220a, 0x00 }, + { 0x220b, 0x00 }, + { 0x220c, 0x00 }, + { 0x220d, 0x04 }, + { 0x220e, 0x00 }, + { 0x220f, 0x00 }, + { 0x2211, 0x01 }, + { 0x2212, 0x00 }, + { 0x2220, 0x00 }, + { 0x2221, 0x00 }, + { 0x2222, 0x00 }, + { 0x2223, 0x00 }, + { 0x2230, 0x00 }, + { 0x2231, 0x2f }, + { 0x2232, 0x80 }, + { 0x2233, 0x00 }, + { 0x2234, 0x00 }, + { 0x2235, 0x00 }, + { 0x2236, 0x00 }, + { 0x2237, 0x00 }, + { 0x2238, 0x00 }, + { 0x2239, 0x00 }, + { 0x2f01, 0x00 }, + { 0x2f02, 0x09 }, + { 0x2f03, 0x00 }, + { 0x2f04, 0x00 }, + { 0x2f05, 0x0b }, + { 0x2f06, 0x01 }, + { 0x2f07, 0xcf }, + { 0x2f08, 0x00 }, + { 0x2f09, 0x00 }, + { 0x2f0a, 0x00 }, + { 0x2f0b, 0x00 }, + { 0x2f0c, 0x00 }, + { 0x2f0d, 0x00 }, + { 0x2f0e, 0x00 }, + { 0x2f0f, 0x00 }, + { 0x3122, 0x00 }, + { 0x3123, 0x00 }, + { 0x7303, 0x57 }, + { 0x8383, 0x57 }, + { 0x7308, 0x97 }, + { 0x8388, 0x97 }, + { 0x7309, 0x97 }, + { 0x8389, 0x97 }, + { 0x7312, 0x00 }, + { 0x8392, 0x00 }, + { 0x7313, 0x00 }, + { 0x8393, 0x00 }, + { 0x7319, 0x00 }, + { 0x8399, 0x00 }, + { 0x752009, 0x1029 }, + { 0x752011, 0x007a }, + { 0x75201a, 0x8003 }, + { 0x752045, 0x5289 }, + { 0x752048, 0xd049 }, + { 0x75204a, 0xa83b }, + { 0x75206b, 0x5064 }, + { 0x75206f, 0x058b }, + { 0x752091, 0x0000 }, +}; + +#endif /* __RT711_SDW_H__ */ diff --git a/sound/soc/codecs/rt711.c b/sound/soc/codecs/rt711.c new file mode 100644 index 00000000000000..4090dc09694d0b --- /dev/null +++ b/sound/soc/codecs/rt711.c @@ -0,0 +1,1289 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// rt711.c -- rt711 ALSA SoC audio driver +// +// Copyright(c) 2019 Realtek Semiconductor Corp. +// +// + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "rt711.h" + +static int rt711_index_write(struct regmap *regmap, + unsigned int nid, unsigned int reg, unsigned int value) +{ + int ret; + unsigned int addr = ((RT711_PRIV_INDEX_W_H | nid) << 8) | reg; + + ret = regmap_write(regmap, addr, value); + if (ret < 0) + pr_err("Failed to set private value: %06x <= %04x ret=%d\n", + addr, value, ret); + + return ret; +} + +static int rt711_index_read(struct regmap *regmap, + unsigned int nid, unsigned int reg, unsigned int *value) +{ + int ret; + unsigned int addr = ((RT711_PRIV_INDEX_W_H | nid) << 8) | reg; + + *value = 0; + ret = regmap_read(regmap, addr, value); + if (ret < 0) + pr_err("Failed to get private value: %06x => %04x ret=%d\n", + addr, *value, ret); + + return ret; +} + +static int rt711_index_update_bits(struct regmap *regmap, unsigned int nid, + unsigned int reg, unsigned int mask, unsigned int val) +{ + unsigned int tmp, orig; + int ret; + + ret = rt711_index_read(regmap, nid, reg, &orig); + if (ret < 0) + return ret; + + tmp = orig & ~mask; + tmp |= val & mask; + + return rt711_index_write(regmap, nid, reg, tmp); +} + +static void rt711_reset(struct regmap *regmap) +{ + regmap_write(regmap, RT711_FUNC_RESET, 0); + rt711_index_update_bits(regmap, RT711_VENDOR_REG, + RT711_PARA_VERB_CTL, RT711_HIDDEN_REG_SW_RESET, + RT711_HIDDEN_REG_SW_RESET); +} + +static int rt711_calibration(struct rt711_priv *rt711) +{ + unsigned int val, loop = 0; + struct device *dev; + struct regmap *regmap = rt711->regmap; + int ret = 0; + + mutex_lock(&rt711->calibrate_mutex); + regmap_write(rt711->regmap, + RT711_SET_AUDIO_POWER_STATE, AC_PWRST_D0); + + dev = regmap_get_device(regmap); + + /* Calibration manual mode */ + rt711_index_update_bits(regmap, RT711_VENDOR_REG, RT711_FSM_CTL, + 0xf, 0x0); + + /* trigger */ + rt711_index_update_bits(regmap, RT711_VENDOR_CALI, + RT711_DAC_DC_CALI_CTL1, RT711_DAC_DC_CALI_TRIGGER, + RT711_DAC_DC_CALI_TRIGGER); + + /* wait for calibration process */ + rt711_index_read(regmap, RT711_VENDOR_CALI, + RT711_DAC_DC_CALI_CTL1, &val); + + while (val & RT711_DAC_DC_CALI_TRIGGER) { + if (loop >= 500) { + pr_err("%s, calibration time-out!\n", + __func__); + ret = -ETIMEDOUT; + break; + } + loop++; + + usleep_range(10000, 11000); + rt711_index_read(regmap, RT711_VENDOR_CALI, + RT711_DAC_DC_CALI_CTL1, &val); + } + + /* depop mode */ + rt711_index_update_bits(regmap, RT711_VENDOR_REG, + RT711_FSM_CTL, 0xf, RT711_DEPOP_CTL); + + regmap_write(rt711->regmap, + RT711_SET_AUDIO_POWER_STATE, AC_PWRST_D3); + mutex_unlock(&rt711->calibrate_mutex); + + dev_dbg(dev, "%s calibration complete, ret=%d\n", __func__, ret); + return ret; +} + +static unsigned int rt711_button_detect(struct rt711_priv *rt711) +{ + unsigned int btn_type = 0, val80, val81; + int ret; + + ret = rt711_index_read(rt711->regmap, RT711_VENDOR_REG, + RT711_IRQ_FLAG_TABLE1, &val80); + if (ret < 0) + goto read_error; + ret = rt711_index_read(rt711->regmap, RT711_VENDOR_REG, + RT711_IRQ_FLAG_TABLE2, &val81); + if (ret < 0) + goto read_error; + + val80 &= 0x0381; + val81 &= 0xff00; + + switch (val80) { + case 0x0200: + case 0x0100: + case 0x0080: + btn_type |= SND_JACK_BTN_0; + break; + case 0x0001: + btn_type |= SND_JACK_BTN_3; + break; + } + switch (val81) { + case 0x8000: + case 0x4000: + case 0x2000: + btn_type |= SND_JACK_BTN_1; + break; + case 0x1000: + case 0x0800: + case 0x0400: + btn_type |= SND_JACK_BTN_2; + break; + case 0x0200: + case 0x0100: + btn_type |= SND_JACK_BTN_3; + break; + } +read_error: + return btn_type; +} + +static int rt711_headset_detect(struct rt711_priv *rt711) +{ + unsigned int buf, loop = 0; + int ret; + + ret = rt711_index_read(rt711->regmap, RT711_VENDOR_REG, + RT711_COMBO_JACK_AUTO_CTL2, &buf); + if (ret < 0) + goto io_error; + + while (loop < 500 && + (buf & RT711_COMBOJACK_AUTO_DET_STATUS) == 0) { + loop++; + + usleep_range(9000, 10000); + ret = rt711_index_read(rt711->regmap, RT711_VENDOR_REG, + RT711_COMBO_JACK_AUTO_CTL2, &buf); + if (ret < 0) + goto io_error; + } + + if (loop >= 500) + goto to_error; + + if (buf & RT711_COMBOJACK_AUTO_DET_TRS) + rt711->jack_type = SND_JACK_HEADPHONE; + else if ((buf & RT711_COMBOJACK_AUTO_DET_CTIA) || + (buf & RT711_COMBOJACK_AUTO_DET_OMTP)) + rt711->jack_type = SND_JACK_HEADSET; + + return 0; + +to_error: + ret = -ETIMEDOUT; + pr_err_ratelimited("Time-out error in %s\n", __func__); + return ret; +io_error: + pr_err_ratelimited("IO error in %s, ret %d\n", __func__, ret); + return ret; +} + +static void rt711_jack_detect_handler(struct work_struct *work) +{ + struct rt711_priv *rt711 = + container_of(work, struct rt711_priv, jack_detect_work.work); + int btn_type = 0, ret; + unsigned int jack_status = 0, reg; + + if (!rt711->hs_jack) + return; + + if (!rt711->component->card->instantiated) + return; + + reg = RT711_VERB_GET_PIN_SENSE | RT711_HP_OUT; + ret = regmap_read(rt711->regmap, reg, &jack_status); + if (ret < 0) + goto io_error; + + /* pin attached */ + if (jack_status & (1 << 31)) { + /* jack in */ + if (rt711->jack_type == 0) { + ret = rt711_headset_detect(rt711); + if (ret < 0) + return; + if (rt711->jack_type == SND_JACK_HEADSET) + btn_type = rt711_button_detect(rt711); + } else if (rt711->jack_type == SND_JACK_HEADSET) { + /* jack is already in, report button event */ + btn_type = rt711_button_detect(rt711); + } + } else { + /* jack out */ + rt711->jack_type = 0; + } + + dev_dbg(&rt711->slave->dev, + "in %s, jack_type=0x%x\n", __func__, rt711->jack_type); + dev_dbg(&rt711->slave->dev, + "in %s, btn_type=0x%x\n", __func__, btn_type); + + snd_soc_jack_report(rt711->hs_jack, rt711->jack_type | btn_type, + SND_JACK_HEADSET | + SND_JACK_BTN_0 | SND_JACK_BTN_1 | + SND_JACK_BTN_2 | SND_JACK_BTN_3); + + if (btn_type) { + /* button released */ + snd_soc_jack_report(rt711->hs_jack, rt711->jack_type, + SND_JACK_HEADSET | + SND_JACK_BTN_0 | SND_JACK_BTN_1 | + SND_JACK_BTN_2 | SND_JACK_BTN_3); + + mod_delayed_work(system_power_efficient_wq, + &rt711->jack_btn_check_work, msecs_to_jiffies(200)); + } + + return; + +io_error: + pr_err_ratelimited("IO error in %s, ret %d\n", __func__, ret); +} + +static void rt711_btn_check_handler(struct work_struct *work) +{ + struct rt711_priv *rt711 = container_of(work, struct rt711_priv, + jack_btn_check_work.work); + int btn_type = 0, ret; + unsigned int jack_status = 0, reg; + + reg = RT711_VERB_GET_PIN_SENSE | RT711_HP_OUT; + ret = regmap_read(rt711->regmap, reg, &jack_status); + if (ret < 0) + goto io_error; + + /* pin attached */ + if (jack_status & (1 << 31)) { + if (rt711->jack_type == SND_JACK_HEADSET) { + /* jack is already in, report button event */ + btn_type = rt711_button_detect(rt711); + } + } else { + rt711->jack_type = 0; + } + + /* cbj comparator */ + ret = rt711_index_read(rt711->regmap, RT711_VENDOR_REG, + RT711_COMBO_JACK_AUTO_CTL2, ®); + if (ret < 0) + goto io_error; + + if ((reg & 0xf0) == 0xf0) + btn_type = 0; + + dev_dbg(&rt711->slave->dev, + "%s, btn_type=0x%x\n", __func__, btn_type); + snd_soc_jack_report(rt711->hs_jack, rt711->jack_type | btn_type, + SND_JACK_HEADSET | + SND_JACK_BTN_0 | SND_JACK_BTN_1 | + SND_JACK_BTN_2 | SND_JACK_BTN_3); + + if (btn_type) { + /* button released */ + snd_soc_jack_report(rt711->hs_jack, rt711->jack_type, + SND_JACK_HEADSET | + SND_JACK_BTN_0 | SND_JACK_BTN_1 | + SND_JACK_BTN_2 | SND_JACK_BTN_3); + + mod_delayed_work(system_power_efficient_wq, + &rt711->jack_btn_check_work, msecs_to_jiffies(200)); + } + + return; + +io_error: + pr_err_ratelimited("IO error in %s, ret %d\n", __func__, ret); +} + +static void rt711_jack_init(struct rt711_priv *rt711) +{ + struct snd_soc_dapm_context *dapm = + snd_soc_component_get_dapm(rt711->component); + + mutex_lock(&rt711->calibrate_mutex); + /* power on */ + if (dapm->bias_level <= SND_SOC_BIAS_STANDBY) + regmap_write(rt711->regmap, + RT711_SET_AUDIO_POWER_STATE, AC_PWRST_D0); + + if (rt711->hs_jack) { + /* unsolicited response & IRQ control */ + regmap_write(rt711->regmap, + RT711_SET_MIC2_UNSOLICITED_ENABLE, 0x82); + regmap_write(rt711->regmap, + RT711_SET_HP_UNSOLICITED_ENABLE, 0x81); + regmap_write(rt711->regmap, + RT711_SET_INLINE_UNSOLICITED_ENABLE, 0x83); + rt711_index_write(rt711->regmap, RT711_VENDOR_REG, + 0x10, 0x2420); + rt711_index_write(rt711->regmap, RT711_VENDOR_REG, + 0x19, 0x2e11); + + switch (rt711->jd_src) { + case RT711_JD1: + /* default settings was already for JD1 */ + break; + case RT711_JD2: + rt711_index_update_bits(rt711->regmap, RT711_VENDOR_REG, + RT711_JD_CTL2, RT711_JD2_2PORT_200K_DECODE_HP | + RT711_HP_JD_SEL_JD2, + RT711_JD2_2PORT_200K_DECODE_HP | + RT711_HP_JD_SEL_JD2); + rt711_index_update_bits(rt711->regmap, RT711_VENDOR_REG, + RT711_CC_DET1, + RT711_HP_JD_FINAL_RESULT_CTL_JD12, + RT711_HP_JD_FINAL_RESULT_CTL_JD12); + break; + default: + dev_warn(rt711->component->dev, "Wrong JD source\n"); + break; + } + + dev_dbg(&rt711->slave->dev, "in %s enable\n", __func__); + + mod_delayed_work(system_power_efficient_wq, + &rt711->jack_detect_work, msecs_to_jiffies(250)); + } else { + regmap_write(rt711->regmap, + RT711_SET_MIC2_UNSOLICITED_ENABLE, 0x00); + regmap_write(rt711->regmap, + RT711_SET_HP_UNSOLICITED_ENABLE, 0x00); + regmap_write(rt711->regmap, + RT711_SET_INLINE_UNSOLICITED_ENABLE, 0x00); + + dev_dbg(&rt711->slave->dev, "in %s disable\n", __func__); + } + + /* power off */ + if (dapm->bias_level <= SND_SOC_BIAS_STANDBY) + regmap_write(rt711->regmap, + RT711_SET_AUDIO_POWER_STATE, AC_PWRST_D3); + mutex_unlock(&rt711->calibrate_mutex); +} + +static int rt711_set_jack_detect(struct snd_soc_component *component, + struct snd_soc_jack *hs_jack, void *data) +{ + struct rt711_priv *rt711 = snd_soc_component_get_drvdata(component); + + rt711->hs_jack = hs_jack; + + if (!rt711->hw_init) { + dev_dbg(&rt711->slave->dev, + "%s hw_init not ready yet\n", __func__); + return 0; + } + + rt711_jack_init(rt711); + + return 0; +} + +static void rt711_get_gain(struct rt711_priv *rt711, unsigned int addr_h, + unsigned int addr_l, unsigned int val_h, + unsigned int *r_val, unsigned int *l_val) +{ + /* R Channel */ + *r_val = (val_h << 8); + regmap_read(rt711->regmap, addr_l, r_val); + + /* L Channel */ + val_h |= 0x20; + *l_val = (val_h << 8); + regmap_read(rt711->regmap, addr_h, l_val); +} + +/* For Verb-Set Amplifier Gain (Verb ID = 3h) */ +static int rt711_set_amp_gain_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct snd_soc_dapm_context *dapm = + snd_soc_component_get_dapm(component); + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + struct rt711_priv *rt711 = snd_soc_component_get_drvdata(component); + unsigned int addr_h, addr_l, val_h, val_ll, val_lr; + unsigned int read_ll, read_rl; + int i; + + /* Can't use update bit function, so read the original value first */ + addr_h = mc->reg; + addr_l = mc->rreg; + if (mc->shift == RT711_DIR_OUT_SFT) /* output */ + val_h = 0x80; + else /* input */ + val_h = 0x0; + + rt711_get_gain(rt711, addr_h, addr_l, val_h, &read_rl, &read_ll); + + /* L Channel */ + if (mc->invert) { + /* for mute/unmute */ + val_ll = (mc->max - ucontrol->value.integer.value[0]) + << RT711_MUTE_SFT; + /* keep gain */ + read_ll = read_ll & 0x7f; + val_ll |= read_ll; + } else { + /* for gain */ + val_ll = ((ucontrol->value.integer.value[0]) & 0x7f); + if (val_ll > mc->max) + val_ll = mc->max; + /* keep mute status */ + read_ll = read_ll & (1 << RT711_MUTE_SFT); + val_ll |= read_ll; + } + + if (dapm->bias_level <= SND_SOC_BIAS_STANDBY) + regmap_write(rt711->regmap, + RT711_SET_AUDIO_POWER_STATE, AC_PWRST_D0); + + /* R Channel */ + if (mc->invert) { + /* for mute/unmute */ + val_lr = (mc->max - ucontrol->value.integer.value[1]) + << RT711_MUTE_SFT; + /* keep gain */ + read_rl = read_rl & 0x7f; + val_lr |= read_rl; + } else { + /* for gain */ + val_lr = ((ucontrol->value.integer.value[1]) & 0x7f); + if (val_lr > mc->max) + val_lr = mc->max; + /* keep mute status */ + read_rl = read_rl & (1 << RT711_MUTE_SFT); + val_lr |= read_rl; + } + + for (i = 0; i < 3; i++) { /* retry 3 times at most */ + + if (val_ll == val_lr) { + /* Set both L/R channels at the same time */ + val_h = (1 << mc->shift) | (3 << 4); + regmap_write(rt711->regmap, + addr_h, (val_h << 8 | val_ll)); + regmap_write(rt711->regmap, + addr_l, (val_h << 8 | val_ll)); + } else { + /* Lch*/ + val_h = (1 << mc->shift) | (1 << 5); + regmap_write(rt711->regmap, + addr_h, (val_h << 8 | val_ll)); + + /* Rch */ + val_h = (1 << mc->shift) | (1 << 4); + regmap_write(rt711->regmap, + addr_l, (val_h << 8 | val_lr)); + } + /* check result */ + if (mc->shift == RT711_DIR_OUT_SFT) /* output */ + val_h = 0x80; + else /* input */ + val_h = 0x0; + + rt711_get_gain(rt711, addr_h, addr_l, val_h, + &read_rl, &read_ll); + if (read_rl == val_lr && read_ll == val_ll) + break; + } + + if (dapm->bias_level <= SND_SOC_BIAS_STANDBY) + regmap_write(rt711->regmap, + RT711_SET_AUDIO_POWER_STATE, AC_PWRST_D3); + return 0; +} + +static int rt711_set_amp_gain_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct rt711_priv *rt711 = snd_soc_component_get_drvdata(component); + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + unsigned int addr_h, addr_l, val_h; + unsigned int read_ll, read_rl; + + /* switch to get command */ + addr_h = mc->reg; + addr_l = mc->rreg; + if (mc->shift == RT711_DIR_OUT_SFT) /* output */ + val_h = 0x80; + else /* input */ + val_h = 0x0; + + rt711_get_gain(rt711, addr_h, addr_l, val_h, &read_rl, &read_ll); + + if (mc->invert) { + /* mute/unmute for switch controls */ + read_ll = !((read_ll & 0x80) >> RT711_MUTE_SFT); + read_rl = !((read_rl & 0x80) >> RT711_MUTE_SFT); + } else { + /* for gain volume controls */ + read_ll = read_ll & 0x7f; + read_rl = read_rl & 0x7f; + } + ucontrol->value.integer.value[0] = read_ll; + ucontrol->value.integer.value[1] = read_rl; + + return 0; +} + +static const DECLARE_TLV_DB_SCALE(out_vol_tlv, -6525, 75, 0); +static const DECLARE_TLV_DB_SCALE(in_vol_tlv, -1725, 75, 0); +static const DECLARE_TLV_DB_SCALE(mic_vol_tlv, 0, 1000, 0); + +static const struct snd_kcontrol_new rt711_snd_controls[] = { + SOC_DOUBLE_R_EXT_TLV("DAC Surr Playback Volume", + RT711_SET_GAIN_DAC2_H, RT711_SET_GAIN_DAC2_L, + RT711_DIR_OUT_SFT, 0x57, 0, + rt711_set_amp_gain_get, rt711_set_amp_gain_put, out_vol_tlv), + SOC_DOUBLE_R_EXT("ADC 08 Capture Switch", + RT711_SET_GAIN_ADC2_H, RT711_SET_GAIN_ADC2_L, + RT711_DIR_IN_SFT, 1, 1, + rt711_set_amp_gain_get, rt711_set_amp_gain_put), + SOC_DOUBLE_R_EXT("ADC 09 Capture Switch", + RT711_SET_GAIN_ADC1_H, RT711_SET_GAIN_ADC1_L, + RT711_DIR_IN_SFT, 1, 1, + rt711_set_amp_gain_get, rt711_set_amp_gain_put), + SOC_DOUBLE_R_EXT_TLV("ADC 08 Capture Volume", + RT711_SET_GAIN_ADC2_H, RT711_SET_GAIN_ADC2_L, + RT711_DIR_IN_SFT, 0x3f, 0, + rt711_set_amp_gain_get, rt711_set_amp_gain_put, in_vol_tlv), + SOC_DOUBLE_R_EXT_TLV("ADC 09 Capture Volume", + RT711_SET_GAIN_ADC1_H, RT711_SET_GAIN_ADC1_L, + RT711_DIR_IN_SFT, 0x3f, 0, + rt711_set_amp_gain_get, rt711_set_amp_gain_put, in_vol_tlv), + SOC_DOUBLE_R_EXT_TLV("AMIC Volume", + RT711_SET_GAIN_AMIC_H, RT711_SET_GAIN_AMIC_L, + RT711_DIR_IN_SFT, 3, 0, + rt711_set_amp_gain_get, rt711_set_amp_gain_put, mic_vol_tlv), + SOC_DOUBLE_R_EXT_TLV("DMIC1 Volume", + RT711_SET_GAIN_DMIC1_H, RT711_SET_GAIN_DMIC1_L, + RT711_DIR_IN_SFT, 3, 0, + rt711_set_amp_gain_get, rt711_set_amp_gain_put, mic_vol_tlv), + SOC_DOUBLE_R_EXT_TLV("DMIC2 Volume", + RT711_SET_GAIN_DMIC2_H, RT711_SET_GAIN_DMIC2_L, + RT711_DIR_IN_SFT, 3, 0, + rt711_set_amp_gain_get, rt711_set_amp_gain_put, mic_vol_tlv), +}; + +static int rt711_mux_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = + snd_soc_dapm_kcontrol_component(kcontrol); + struct rt711_priv *rt711 = snd_soc_component_get_drvdata(component); + unsigned int reg, val = 0, nid; + int ret; + + if (strstr(ucontrol->id.name, "ADC 22 Mux")) + nid = RT711_MIXER_IN1; + else if (strstr(ucontrol->id.name, "ADC 23 Mux")) + nid = RT711_MIXER_IN2; + else + return -EINVAL; + + /* vid = 0xf01 */ + reg = RT711_VERB_SET_CONNECT_SEL | nid; + ret = regmap_read(rt711->regmap, reg, &val); + if (ret < 0) { + dev_err(component->dev, "%s: sdw read failed: %d\n", + __func__, ret); + return ret; + } + + ucontrol->value.enumerated.item[0] = val; + + return 0; +} + +static int rt711_mux_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = + snd_soc_dapm_kcontrol_component(kcontrol); + struct snd_soc_dapm_context *dapm = + snd_soc_dapm_kcontrol_dapm(kcontrol); + struct rt711_priv *rt711 = snd_soc_component_get_drvdata(component); + struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; + unsigned int *item = ucontrol->value.enumerated.item; + unsigned int val, val2 = 0, change, reg, nid; + int ret; + + if (item[0] >= e->items) + return -EINVAL; + + if (strstr(ucontrol->id.name, "ADC 22 Mux")) + nid = RT711_MIXER_IN1; + else if (strstr(ucontrol->id.name, "ADC 23 Mux")) + nid = RT711_MIXER_IN2; + else + return -EINVAL; + + /* Verb ID = 0x701h */ + val = snd_soc_enum_item_to_val(e, item[0]) << e->shift_l; + + reg = RT711_VERB_SET_CONNECT_SEL | nid; + ret = regmap_read(rt711->regmap, reg, &val2); + if (ret < 0) { + dev_err(component->dev, "%s: sdw read failed: %d\n", + __func__, ret); + return ret; + } + + if (val == val2) + change = 0; + else + change = 1; + + if (change) { + reg = RT711_VERB_SET_CONNECT_SEL | nid; + regmap_write(rt711->regmap, reg, val); + } + + snd_soc_dapm_mux_update_power(dapm, kcontrol, + item[0], e, NULL); + + return change; +} + +static const char * const adc_mux_text[] = { + "MIC2", + "LINE1", + "LINE2", + "DMIC", +}; + +static SOC_ENUM_SINGLE_DECL( + rt711_adc22_enum, SND_SOC_NOPM, 0, adc_mux_text); + +static SOC_ENUM_SINGLE_DECL( + rt711_adc23_enum, SND_SOC_NOPM, 0, adc_mux_text); + +static const struct snd_kcontrol_new rt711_adc22_mux = + SOC_DAPM_ENUM_EXT("ADC 22 Mux", rt711_adc22_enum, + rt711_mux_get, rt711_mux_put); + +static const struct snd_kcontrol_new rt711_adc23_mux = + SOC_DAPM_ENUM_EXT("ADC 23 Mux", rt711_adc23_enum, + rt711_mux_get, rt711_mux_put); + +static int rt711_dac_surround_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = + snd_soc_dapm_to_component(w->dapm); + struct rt711_priv *rt711 = snd_soc_component_get_drvdata(component); + unsigned int val_h = (1 << RT711_DIR_OUT_SFT) | (0x3 << 4); + unsigned int val_l; + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + regmap_write(rt711->regmap, + RT711_SET_STREAMID_DAC2, 0x10); + + val_l = 0x00; + regmap_write(rt711->regmap, + RT711_SET_GAIN_HP_H, (val_h << 8 | val_l)); + break; + case SND_SOC_DAPM_PRE_PMD: + val_l = (1 << RT711_MUTE_SFT); + regmap_write(rt711->regmap, + RT711_SET_GAIN_HP_H, (val_h << 8 | val_l)); + usleep_range(50000, 55000); + + regmap_write(rt711->regmap, + RT711_SET_STREAMID_DAC2, 0x00); + break; + } + return 0; +} + +static int rt711_adc_09_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = + snd_soc_dapm_to_component(w->dapm); + struct rt711_priv *rt711 = snd_soc_component_get_drvdata(component); + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + regmap_write(rt711->regmap, + RT711_SET_STREAMID_ADC1, 0x10); + break; + case SND_SOC_DAPM_PRE_PMD: + regmap_write(rt711->regmap, + RT711_SET_STREAMID_ADC1, 0x00); + break; + } + return 0; +} + +static int rt711_adc_08_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = + snd_soc_dapm_to_component(w->dapm); + struct rt711_priv *rt711 = snd_soc_component_get_drvdata(component); + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + regmap_write(rt711->regmap, + RT711_SET_STREAMID_ADC2, 0x10); + break; + case SND_SOC_DAPM_PRE_PMD: + regmap_write(rt711->regmap, + RT711_SET_STREAMID_ADC2, 0x00); + break; + } + return 0; +} + +static const struct snd_soc_dapm_widget rt711_dapm_widgets[] = { + SND_SOC_DAPM_OUTPUT("HP"), + SND_SOC_DAPM_INPUT("MIC2"), + SND_SOC_DAPM_INPUT("DMIC1"), + SND_SOC_DAPM_INPUT("DMIC2"), + SND_SOC_DAPM_INPUT("LINE1"), + SND_SOC_DAPM_INPUT("LINE2"), + + SND_SOC_DAPM_DAC_E("DAC Surround", NULL, SND_SOC_NOPM, 0, 0, + rt711_dac_surround_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), + SND_SOC_DAPM_ADC_E("ADC 09", NULL, SND_SOC_NOPM, 0, 0, + rt711_adc_09_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), + SND_SOC_DAPM_ADC_E("ADC 08", NULL, SND_SOC_NOPM, 0, 0, + rt711_adc_08_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), + SND_SOC_DAPM_MUX("ADC 22 Mux", SND_SOC_NOPM, 0, 0, + &rt711_adc22_mux), + SND_SOC_DAPM_MUX("ADC 23 Mux", SND_SOC_NOPM, 0, 0, + &rt711_adc23_mux), + + SND_SOC_DAPM_AIF_IN("DP3RX", "DP3 Playback", 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("DP2TX", "DP2 Capture", 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("DP4TX", "DP4 Capture", 0, SND_SOC_NOPM, 0, 0), +}; + +static const struct snd_soc_dapm_route rt711_audio_map[] = { + {"DAC Surround", NULL, "DP3RX"}, + {"DP2TX", NULL, "ADC 09"}, + {"DP4TX", NULL, "ADC 08"}, + + {"ADC 09", NULL, "ADC 22 Mux"}, + {"ADC 08", NULL, "ADC 23 Mux"}, + {"ADC 22 Mux", "DMIC", "DMIC1"}, + {"ADC 22 Mux", "LINE1", "LINE1"}, + {"ADC 22 Mux", "LINE2", "LINE2"}, + {"ADC 22 Mux", "MIC2", "MIC2"}, + {"ADC 23 Mux", "DMIC", "DMIC2"}, + {"ADC 23 Mux", "LINE1", "LINE1"}, + {"ADC 23 Mux", "LINE2", "LINE2"}, + {"ADC 23 Mux", "MIC2", "MIC2"}, + + {"HP", NULL, "DAC Surround"}, +}; + +static int rt711_set_bias_level(struct snd_soc_component *component, + enum snd_soc_bias_level level) +{ + struct snd_soc_dapm_context *dapm = + snd_soc_component_get_dapm(component); + struct rt711_priv *rt711 = snd_soc_component_get_drvdata(component); + + switch (level) { + case SND_SOC_BIAS_PREPARE: + if (dapm->bias_level == SND_SOC_BIAS_STANDBY) { + regmap_write(rt711->regmap, + RT711_SET_AUDIO_POWER_STATE, + AC_PWRST_D0); + } + break; + + case SND_SOC_BIAS_STANDBY: + regmap_write(rt711->regmap, + RT711_SET_AUDIO_POWER_STATE, + AC_PWRST_D3); + break; + + default: + break; + } + + return 0; +} + +static int rt711_parse_dt(struct rt711_priv *rt711, struct device *dev) +{ + device_property_read_u32(dev, "realtek,jd-src", + &rt711->jd_src); + + return 0; +} + +static int rt711_probe(struct snd_soc_component *component) +{ + struct rt711_priv *rt711 = snd_soc_component_get_drvdata(component); + + rt711_parse_dt(rt711, &rt711->slave->dev); + rt711->component = component; + + return 0; +} + +static const struct snd_soc_component_driver soc_codec_dev_rt711 = { + .probe = rt711_probe, + .set_bias_level = rt711_set_bias_level, + .controls = rt711_snd_controls, + .num_controls = ARRAY_SIZE(rt711_snd_controls), + .dapm_widgets = rt711_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(rt711_dapm_widgets), + .dapm_routes = rt711_audio_map, + .num_dapm_routes = ARRAY_SIZE(rt711_audio_map), + .set_jack = rt711_set_jack_detect, +}; + +static int rt711_set_sdw_stream(struct snd_soc_dai *dai, void *sdw_stream, + int direction) +{ + struct sdw_stream_data *stream; + + stream = kzalloc(sizeof(*stream), GFP_KERNEL); + if (!stream) + return -ENOMEM; + + stream->sdw_stream = (struct sdw_stream_runtime *)sdw_stream; + + /* Use tx_mask or rx_mask to configure stream tag and set dma_data */ + if (direction == SNDRV_PCM_STREAM_PLAYBACK) + dai->playback_dma_data = stream; + else + dai->capture_dma_data = stream; + + return 0; +} + +static void rt711_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct sdw_stream_data *stream; + + stream = snd_soc_dai_get_dma_data(dai, substream); + snd_soc_dai_set_dma_data(dai, substream, NULL); + kfree(stream); +} + +static int rt711_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct rt711_priv *rt711 = snd_soc_component_get_drvdata(component); + struct sdw_stream_config stream_config; + struct sdw_port_config port_config; + enum sdw_data_direction direction; + struct sdw_stream_data *stream; + int retval, port, num_channels; + unsigned int val = 0; + + dev_dbg(dai->dev, "%s %s", __func__, dai->name); + stream = snd_soc_dai_get_dma_data(dai, substream); + + if (!stream) + return -EINVAL; + + if (!rt711->slave) + return -EINVAL; + + /* SoundWire specific configuration */ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + direction = SDW_DATA_DIR_RX; + port = 3; + } else { + direction = SDW_DATA_DIR_TX; + if (dai->id == RT711_AIF1) + port = 4; + else if (dai->id == RT711_AIF2) + port = 2; + else + return -EINVAL; + } + + stream_config.frame_rate = params_rate(params); + stream_config.ch_count = params_channels(params); + stream_config.bps = snd_pcm_format_width(params_format(params)); + stream_config.direction = direction; + + num_channels = params_channels(params); + port_config.ch_mask = (1 << (num_channels)) - 1; + port_config.num = port; + + retval = sdw_stream_add_slave(rt711->slave, &stream_config, + &port_config, 1, stream->sdw_stream); + if (retval) { + dev_err(dai->dev, "Unable to configure port\n"); + return retval; + } + + if (params_channels(params) <= 16) { + /* bit 3:0 Number of Channel */ + val |= (params_channels(params) - 1); + } else { + dev_err(component->dev, "Unsupported channels %d\n", + params_channels(params)); + return -EINVAL; + } + + switch (params_width(params)) { + /* bit 6:4 Bits per Sample */ + case 8: + break; + case 16: + val |= (0x1 << 4); + break; + case 20: + val |= (0x2 << 4); + break; + case 24: + val |= (0x3 << 4); + break; + case 32: + val |= (0x4 << 4); + break; + default: + return -EINVAL; + } + + /* 48Khz */ + regmap_write(rt711->regmap, RT711_DAC_FORMAT_H, val); + regmap_write(rt711->regmap, RT711_ADC1_FORMAT_H, val); + regmap_write(rt711->regmap, RT711_ADC2_FORMAT_H, val); + + return retval; +} + +static int rt711_pcm_hw_free(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct rt711_priv *rt711 = snd_soc_component_get_drvdata(component); + struct sdw_stream_data *stream = + snd_soc_dai_get_dma_data(dai, substream); + + if (!rt711->slave) + return -EINVAL; + + sdw_stream_remove_slave(rt711->slave, stream->sdw_stream); + return 0; +} + +#define RT711_STEREO_RATES (SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000) +#define RT711_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \ + SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S8) + +static struct snd_soc_dai_ops rt711_ops = { + .hw_params = rt711_pcm_hw_params, + .hw_free = rt711_pcm_hw_free, + .set_sdw_stream = rt711_set_sdw_stream, + .shutdown = rt711_shutdown, +}; + +static struct snd_soc_dai_driver rt711_dai[] = { + { + .name = "rt711-aif1", + .id = RT711_AIF1, + .playback = { + .stream_name = "DP3 Playback", + .channels_min = 1, + .channels_max = 2, + .rates = RT711_STEREO_RATES, + .formats = RT711_FORMATS, + }, + .capture = { + .stream_name = "DP4 Capture", + .channels_min = 1, + .channels_max = 2, + .rates = RT711_STEREO_RATES, + .formats = RT711_FORMATS, + }, + .ops = &rt711_ops, + }, + { + .name = "rt711-aif2", + .id = RT711_AIF2, + .capture = { + .stream_name = "DP2 Capture", + .channels_min = 1, + .channels_max = 2, + .rates = RT711_STEREO_RATES, + .formats = RT711_FORMATS, + }, + .ops = &rt711_ops, + } +}; + +/* Bus clock frequency */ +#define RT711_CLK_FREQ_9600000HZ 9600000 +#define RT711_CLK_FREQ_12000000HZ 12000000 +#define RT711_CLK_FREQ_6000000HZ 6000000 +#define RT711_CLK_FREQ_4800000HZ 4800000 +#define RT711_CLK_FREQ_2400000HZ 2400000 +#define RT711_CLK_FREQ_12288000HZ 12288000 + +int rt711_clock_config(struct device *dev) +{ + struct rt711_priv *rt711 = dev_get_drvdata(dev); + unsigned int clk_freq, value; + + clk_freq = (rt711->params.curr_dr_freq >> 1); + + switch (clk_freq) { + case RT711_CLK_FREQ_12000000HZ: + value = 0x0; + break; + case RT711_CLK_FREQ_6000000HZ: + value = 0x1; + break; + case RT711_CLK_FREQ_9600000HZ: + value = 0x2; + break; + case RT711_CLK_FREQ_4800000HZ: + value = 0x3; + break; + case RT711_CLK_FREQ_2400000HZ: + value = 0x4; + break; + case RT711_CLK_FREQ_12288000HZ: + value = 0x5; + break; + default: + return -EINVAL; + } + + regmap_write(rt711->regmap, 0xe0, value); + regmap_write(rt711->regmap, 0xf0, value); + + dev_dbg(dev, "%s complete, clk_freq=%d\n", __func__, clk_freq); + + return 0; +} + +static void rt711_calibration_work(struct work_struct *work) +{ + struct rt711_priv *rt711 = + container_of(work, struct rt711_priv, calibration_work); + + rt711_calibration(rt711); +} + +int rt711_init(struct device *dev, struct regmap *sdw_regmap, + struct regmap *regmap, struct sdw_slave *slave) +{ + struct rt711_priv *rt711; + int ret; + + rt711 = devm_kzalloc(dev, sizeof(*rt711), GFP_KERNEL); + if (!rt711) + return -ENOMEM; + + dev_set_drvdata(dev, rt711); + rt711->slave = slave; + rt711->sdw_regmap = sdw_regmap; + rt711->regmap = regmap; + + /* + * Mark hw_init to false + * HW init will be performed when device reports present + */ + rt711->hw_init = false; + rt711->first_init = false; + + /* JD source uses JD2 in default */ + rt711->jd_src = RT711_JD2; + + ret = devm_snd_soc_register_component(dev, + &soc_codec_dev_rt711, + rt711_dai, + ARRAY_SIZE(rt711_dai)); + + dev_dbg(&slave->dev, "%s\n", __func__); + + return ret; +} + +int rt711_io_init(struct device *dev, struct sdw_slave *slave) +{ + struct rt711_priv *rt711 = dev_get_drvdata(dev); + + if (rt711->hw_init) + return 0; + + if (rt711->first_init) { + regcache_cache_only(rt711->regmap, false); + regcache_cache_bypass(rt711->regmap, true); + } + + /* + * PM runtime is only enabled when a Slave reports as Attached + */ + if (!rt711->first_init) { + /* set autosuspend parameters */ + pm_runtime_set_autosuspend_delay(&slave->dev, 3000); + pm_runtime_use_autosuspend(&slave->dev); + + /* update count of parent 'active' children */ + pm_runtime_set_active(&slave->dev); + + /* make sure the device does not suspend immediately */ + pm_runtime_mark_last_busy(&slave->dev); + + pm_runtime_enable(&slave->dev); + } + + pm_runtime_get_noresume(&slave->dev); + + rt711_reset(rt711->regmap); + + /* power on */ + regmap_write(rt711->regmap, RT711_SET_AUDIO_POWER_STATE, AC_PWRST_D0); + + /* Set Pin Widget */ + regmap_write(rt711->regmap, RT711_SET_PIN_MIC2, 0x25); + regmap_write(rt711->regmap, RT711_SET_PIN_HP, 0xc0); + regmap_write(rt711->regmap, RT711_SET_PIN_DMIC1, 0x20); + regmap_write(rt711->regmap, RT711_SET_PIN_DMIC2, 0x20); + regmap_write(rt711->regmap, RT711_SET_PIN_LINE1, 0x20); + regmap_write(rt711->regmap, RT711_SET_PIN_LINE2, 0x20); + + /* Mute HP/ADC1/ADC2 */ + regmap_write(rt711->regmap, RT711_SET_GAIN_HP_H, 0xa080); + regmap_write(rt711->regmap, RT711_SET_GAIN_HP_H, 0x9080); + regmap_write(rt711->regmap, RT711_SET_GAIN_ADC2_H, 0x6080); + regmap_write(rt711->regmap, RT711_SET_GAIN_ADC2_H, 0x5080); + regmap_write(rt711->regmap, RT711_SET_GAIN_ADC1_H, 0x6080); + regmap_write(rt711->regmap, RT711_SET_GAIN_ADC1_H, 0x5080); + + /* Set Configuration Default */ + regmap_write(rt711->regmap, 0x4f12, 0x91); + regmap_write(rt711->regmap, 0x4e12, 0xd6); + regmap_write(rt711->regmap, 0x4d12, 0x11); + regmap_write(rt711->regmap, 0x4c12, 0x20); + regmap_write(rt711->regmap, 0x4f13, 0x91); + regmap_write(rt711->regmap, 0x4e13, 0xd6); + regmap_write(rt711->regmap, 0x4d13, 0x11); + regmap_write(rt711->regmap, 0x4c13, 0x21); + regmap_write(rt711->regmap, 0x4c21, 0xf0); + regmap_write(rt711->regmap, 0x4d21, 0x11); + regmap_write(rt711->regmap, 0x4e21, 0x11); + regmap_write(rt711->regmap, 0x4f21, 0x01); + + /* Data port arrangement */ + rt711_index_write(rt711->regmap, RT711_VENDOR_REG, + RT711_TX_RX_MUX_CTL, 0x0154); + + /* Set index */ + rt711_index_write(rt711->regmap, RT711_VENDOR_REG, + RT711_DIGITAL_MISC_CTRL4, 0x201b); + rt711_index_write(rt711->regmap, RT711_VENDOR_REG, + RT711_COMBO_JACK_AUTO_CTL1, 0x5089); + rt711_index_write(rt711->regmap, RT711_VENDOR_REG, + RT711_VREFOUT_CTL, 0x5064); + rt711_index_write(rt711->regmap, RT711_VENDOR_REG, + RT711_INLINE_CMD_CTL, 0xd249); + + /* Finish Initial Settings, set power to D3 */ + regmap_write(rt711->regmap, RT711_SET_AUDIO_POWER_STATE, AC_PWRST_D3); + + if (rt711->first_init) + rt711_calibration(rt711); + else { + INIT_DELAYED_WORK(&rt711->jack_detect_work, + rt711_jack_detect_handler); + INIT_DELAYED_WORK(&rt711->jack_btn_check_work, + rt711_btn_check_handler); + mutex_init(&rt711->calibrate_mutex); + INIT_WORK(&rt711->calibration_work, rt711_calibration_work); + schedule_work(&rt711->calibration_work); + } + + /* + * if set_jack callback occurred early than io_init, + * we set up the jack detection function now + */ + if (rt711->hs_jack) + rt711_jack_init(rt711); + + if (rt711->first_init) + regcache_cache_bypass(rt711->regmap, false); + else + rt711->first_init = true; + + /* Mark Slave initialization complete */ + rt711->hw_init = true; + + pm_runtime_mark_last_busy(&slave->dev); + pm_runtime_put_autosuspend(&slave->dev); + + dev_dbg(&slave->dev, "%s hw_init complete\n", __func__); + return 0; +} + +MODULE_DESCRIPTION("ASoC RT711 SDW driver"); +MODULE_AUTHOR("Shuming Fan "); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/rt711.h b/sound/soc/codecs/rt711.h new file mode 100644 index 00000000000000..ac7509bfa32d8b --- /dev/null +++ b/sound/soc/codecs/rt711.h @@ -0,0 +1,229 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * rt711.h -- RT711 ALSA SoC audio driver header + * + * Copyright(c) 2019 Realtek Semiconductor Corp. + */ + +#ifndef __RT711_H__ +#define __RT711_H__ + +#include + +extern const struct dev_pm_ops rt711_runtime_pm; + +struct rt711_priv { + struct regmap *regmap; + struct regmap *sdw_regmap; + struct snd_soc_component *component; + struct sdw_slave *slave; + enum sdw_slave_status status; + struct sdw_bus_params params; + bool hw_init; + bool first_init; + struct snd_soc_jack *hs_jack; + struct delayed_work jack_detect_work; + struct delayed_work jack_btn_check_work; + struct work_struct calibration_work; + struct mutex calibrate_mutex; /* for headset calibration */ + int jack_type, jd_src; +}; + +struct sdw_stream_data { + struct sdw_stream_runtime *sdw_stream; +}; + +/* NID */ +#define RT711_AUDIO_FUNCTION_GROUP 0x01 +#define RT711_DAC_OUT2 0x03 +#define RT711_ADC_IN1 0x09 +#define RT711_ADC_IN2 0x08 +#define RT711_DMIC1 0x12 +#define RT711_DMIC2 0x13 +#define RT711_MIC2 0x19 +#define RT711_LINE1 0x1a +#define RT711_LINE2 0x1b +#define RT711_BEEP 0x1d +#define RT711_VENDOR_REG 0x20 +#define RT711_HP_OUT 0x21 +#define RT711_MIXER_IN1 0x22 +#define RT711_MIXER_IN2 0x23 +#define RT711_INLINE_CMD 0x55 +#define RT711_VENDOR_CALI 0x58 +#define RT711_VENDOR_IMS_DRE 0x5b + +/* Index (NID:20h) */ +#define RT711_DAC_DC_CALI_CTL1 0x00 +#define RT711_JD_CTL2 0x09 +#define RT711_CC_DET1 0x11 +#define RT711_PARA_VERB_CTL 0x1a +#define RT711_COMBO_JACK_AUTO_CTL1 0x45 +#define RT711_COMBO_JACK_AUTO_CTL2 0x46 +#define RT711_INLINE_CMD_CTL 0x48 +#define RT711_DIGITAL_MISC_CTRL4 0x4a +#define RT711_VREFOUT_CTL 0x6b +#define RT711_FSM_CTL 0x6f +#define RT711_IRQ_FLAG_TABLE1 0x80 +#define RT711_IRQ_FLAG_TABLE2 0x81 +#define RT711_IRQ_FLAG_TABLE3 0x82 +#define RT711_TX_RX_MUX_CTL 0x91 + +/* Index (NID:5bh) */ +#define RT711_IMS_DIGITAL_CTL1 0x00 +#define RT711_HP_IMS_RESULT_L 0x20 +#define RT711_HP_IMS_RESULT_R 0x21 + +/* Verb */ +#define RT711_VERB_SET_CONNECT_SEL 0x3100 +#define RT711_VERB_SET_EAPD_BTLENABLE 0x3c00 +#define RT711_VERB_GET_CONNECT_SEL 0xb100 +#define RT711_VERB_SET_POWER_STATE 0x3500 +#define RT711_VERB_SET_CHANNEL_STREAMID 0x3600 +#define RT711_VERB_SET_PIN_WIDGET_CONTROL 0x3700 +#define RT711_VERB_SET_UNSOLICITED_ENABLE 0x3800 +#define RT711_SET_AMP_GAIN_MUTE_H 0x7300 +#define RT711_SET_AMP_GAIN_MUTE_L 0x8380 +#define RT711_VERB_GET_POWER_STATE 0xb500 +#define RT711_VERB_GET_CHANNEL_STREAMID 0xb600 +#define RT711_VERB_GET_PIN_SENSE 0xb900 +#define RT711_FUNC_RESET 0xff01 + +#define RT711_READ_HDA_3 0x2012 +#define RT711_READ_HDA_2 0x2013 +#define RT711_READ_HDA_1 0x2014 +#define RT711_READ_HDA_0 0x2015 +#define RT711_PRIV_INDEX_W_H 0x7500 +#define RT711_PRIV_INDEX_W_L 0x8580 +#define RT711_PRIV_DATA_W_H 0x7400 +#define RT711_PRIV_DATA_W_L 0x8480 +#define RT711_PRIV_INDEX_R_H 0x9d00 +#define RT711_PRIV_INDEX_R_L 0xad80 +#define RT711_PRIV_DATA_R_H 0x9c00 +#define RT711_PRIV_DATA_R_L 0xac80 +#define RT711_DAC_FORMAT_H 0x7203 +#define RT711_DAC_FORMAT_L 0x8283 +#define RT711_ADC1_FORMAT_H 0x7209 +#define RT711_ADC1_FORMAT_L 0x8289 +#define RT711_ADC2_FORMAT_H 0x7208 +#define RT711_ADC2_FORMAT_L 0x8288 + +#define RT711_SET_AUDIO_POWER_STATE\ + (RT711_VERB_SET_POWER_STATE | RT711_AUDIO_FUNCTION_GROUP) +#define RT711_GET_AUDIO_POWER_STATE\ + (RT711_VERB_GET_POWER_STATE | RT711_AUDIO_FUNCTION_GROUP) +#define RT711_SET_PIN_DMIC1\ + (RT711_VERB_SET_PIN_WIDGET_CONTROL | RT711_DMIC1) +#define RT711_SET_PIN_DMIC2\ + (RT711_VERB_SET_PIN_WIDGET_CONTROL | RT711_DMIC2) +#define RT711_SET_PIN_HP\ + (RT711_VERB_SET_PIN_WIDGET_CONTROL | RT711_HP_OUT) +#define RT711_SET_PIN_MIC2\ + (RT711_VERB_SET_PIN_WIDGET_CONTROL | RT711_MIC2) +#define RT711_SET_PIN_LINE1\ + (RT711_VERB_SET_PIN_WIDGET_CONTROL | RT711_LINE1) +#define RT711_SET_PIN_LINE2\ + (RT711_VERB_SET_PIN_WIDGET_CONTROL | RT711_LINE2) +#define RT711_SET_MIC2_UNSOLICITED_ENABLE\ + (RT711_VERB_SET_UNSOLICITED_ENABLE | RT711_MIC2) +#define RT711_SET_HP_UNSOLICITED_ENABLE\ + (RT711_VERB_SET_UNSOLICITED_ENABLE | RT711_HP_OUT) +#define RT711_SET_INLINE_UNSOLICITED_ENABLE\ + (RT711_VERB_SET_UNSOLICITED_ENABLE | RT711_INLINE_CMD) +#define RT711_SET_STREAMID_DAC2\ + (RT711_VERB_SET_CHANNEL_STREAMID | RT711_DAC_OUT2) +#define RT711_SET_STREAMID_ADC1\ + (RT711_VERB_SET_CHANNEL_STREAMID | RT711_ADC_IN1) +#define RT711_SET_STREAMID_ADC2\ + (RT711_VERB_SET_CHANNEL_STREAMID | RT711_ADC_IN2) +#define RT711_GET_STREAMID_DAC2\ + (RT711_VERB_GET_CHANNEL_STREAMID | RT711_DAC_OUT2) +#define RT711_GET_STREAMID_ADC1\ + (RT711_VERB_GET_CHANNEL_STREAMID | RT711_ADC_IN1) +#define RT711_GET_STREAMID_ADC2\ + (RT711_VERB_GET_CHANNEL_STREAMID | RT711_ADC_IN2) +#define RT711_SET_GAIN_DAC2_L\ + (RT711_SET_AMP_GAIN_MUTE_L | RT711_DAC_OUT2) +#define RT711_SET_GAIN_DAC2_H\ + (RT711_SET_AMP_GAIN_MUTE_H | RT711_DAC_OUT2) +#define RT711_SET_GAIN_ADC1_L\ + (RT711_SET_AMP_GAIN_MUTE_L | RT711_ADC_IN1) +#define RT711_SET_GAIN_ADC1_H\ + (RT711_SET_AMP_GAIN_MUTE_H | RT711_ADC_IN1) +#define RT711_SET_GAIN_ADC2_L\ + (RT711_SET_AMP_GAIN_MUTE_L | RT711_ADC_IN2) +#define RT711_SET_GAIN_ADC2_H\ + (RT711_SET_AMP_GAIN_MUTE_H | RT711_ADC_IN2) +#define RT711_SET_GAIN_AMIC_L\ + (RT711_SET_AMP_GAIN_MUTE_L | RT711_MIC2) +#define RT711_SET_GAIN_AMIC_H\ + (RT711_SET_AMP_GAIN_MUTE_H | RT711_MIC2) +#define RT711_SET_GAIN_DMIC1_L\ + (RT711_SET_AMP_GAIN_MUTE_L | RT711_DMIC1) +#define RT711_SET_GAIN_DMIC1_H\ + (RT711_SET_AMP_GAIN_MUTE_H | RT711_DMIC1) +#define RT711_SET_GAIN_DMIC2_L\ + (RT711_SET_AMP_GAIN_MUTE_L | RT711_DMIC2) +#define RT711_SET_GAIN_DMIC2_H\ + (RT711_SET_AMP_GAIN_MUTE_H | RT711_DMIC2) +#define RT711_SET_GAIN_HP_L\ + (RT711_SET_AMP_GAIN_MUTE_L | RT711_HP_OUT) +#define RT711_SET_GAIN_HP_H\ + (RT711_SET_AMP_GAIN_MUTE_H | RT711_HP_OUT) + +/* DAC DC offset calibration control-1 (0x00)(NID:20h) */ +#define RT711_DAC_DC_CALI_TRIGGER (0x1 << 15) + +/* jack detect control 2 (0x09)(NID:20h) */ +#define RT711_JD2_2PORT_200K_DECODE_HP (0x1 << 13) +#define RT711_HP_JD_SEL_JD1 (0x0 << 1) +#define RT711_HP_JD_SEL_JD2 (0x1 << 1) + +/* CC DET1 (0x11)(NID:20h) */ +#define RT711_HP_JD_FINAL_RESULT_CTL_JD12 (0x1 << 10) +#define RT711_HP_JD_FINAL_RESULT_CTL_CCDET (0x0 << 10) + +/* Parameter & Verb control (0x1a)(NID:20h) */ +#define RT711_HIDDEN_REG_SW_RESET (0x1 << 14) + +/* combo jack auto switch control 2 (0x46)(NID:20h) */ +#define RT711_COMBOJACK_AUTO_DET_STATUS (0x1 << 11) +#define RT711_COMBOJACK_AUTO_DET_TRS (0x1 << 10) +#define RT711_COMBOJACK_AUTO_DET_CTIA (0x1 << 9) +#define RT711_COMBOJACK_AUTO_DET_OMTP (0x1 << 8) + +/* FSM control (0x6f)(NID:20h) */ +#define RT711_CALI_CTL (0x0 << 0) +#define RT711_COMBOJACK_CTL (0x1 << 0) +#define RT711_IMS_CTL (0x2 << 0) +#define RT711_DEPOP_CTL (0x3 << 0) + +/* Impedance Sense Digital Control 1 (0x00)(NID:5bh) */ +#define RT711_TRIGGER_IMS (0x1 << 15) +#define RT711_IMS_EN (0x1 << 6) + +#define RT711_EAPD_HIGH 0x2 +#define RT711_EAPD_LOW 0x0 +#define RT711_MUTE_SFT 7 +/* set input/output mapping to payload[14][15] separately */ +#define RT711_DIR_IN_SFT 6 +#define RT711_DIR_OUT_SFT 7 + +enum { + RT711_AIF1, + RT711_AIF2, + RT711_AIFS, +}; + +enum rt711_jd_src { + RT711_JD_NULL, + RT711_JD1, + RT711_JD2 +}; + +int rt711_io_init(struct device *dev, struct sdw_slave *slave); +int rt711_init(struct device *dev, struct regmap *sdw_regmap, + struct regmap *regmap, struct sdw_slave *slave); + +int rt711_jack_detect(struct rt711_priv *rt711, bool *hp, bool *mic); +int rt711_clock_config(struct device *dev); +#endif /* __RT711_H__ */ diff --git a/sound/soc/codecs/rt715-sdw.c b/sound/soc/codecs/rt715-sdw.c new file mode 100644 index 00000000000000..096c9f3bcf1dc4 --- /dev/null +++ b/sound/soc/codecs/rt715-sdw.c @@ -0,0 +1,551 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * rt715-sdw.c -- rt715 ALSA SoC audio driver + * + * Copyright(c) 2019 Realtek Semiconductor Corp. + * + * ALC715 ASoC Codec Driver based Intel Dummy SdW codec driver + * + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "rt715.h" +#include "rt715-sdw.h" + +static bool rt715_readable_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case 0x02e0: + case 0x02f0: + case 0x04e0: + case 0x04f0: + case 0x06e0: + case 0x06f0: + case 0x00e0 ... 0x00e5: + case 0x00ee ... 0x00ef: + case 0x00f0 ... 0x00f5: + case 0x00fe ... 0x00ff: + case 0x2000 ... 0x2027: + case 0x2029 ... 0x202a: + case 0x202d ... 0x2034: + case 0x2200 ... 0x2204: + case 0x2206 ... 0x2212: + case 0x2220 ... 0x2223: + case 0x2230 ... 0x2239: + case 0x22f0 ... 0x22f3: + case 0x3000 ... 0x3fff: + case 0x7000 ... 0x7fff: + case 0x8300 ... 0x83ff: + case 0x9c00 ... 0x9cff: + case 0xb900 ... 0xb9ff: + case 0x752039: + return true; + default: + return false; + } +} + +static bool rt715_volatile_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case 0x00e5: + case 0x00f0: + case 0x00f3: + case 0x00f5: + case 0x2009: + case 0x2016: + case 0x201b: + case 0x201c: + case 0x201d: + case 0x201f: + case 0x2023: + case 0x2230: + case 0x200b ... 0x200e: /* i2c read */ + case 0x2012 ... 0x2015: /* HD-A read */ + case 0x202d ... 0x202f: /* BRA */ + case 0x2201 ... 0x2212: /* i2c debug */ + case 0x2220 ... 0x2223: /* decoded HD-A */ + case 0x9c00 ... 0x9cff: + case 0xb900 ... 0xb9ff: + return true; + default: + return false; + } +} + +static int rt715_sdw_read(void *context, unsigned int reg, unsigned int *val) +{ + struct device *dev = context; + struct rt715_priv *rt715 = dev_get_drvdata(dev); + unsigned int sdw_data_3, sdw_data_2, sdw_data_1, sdw_data_0; + unsigned int reg2 = 0, reg3 = 0, reg4 = 0, mask, nid, val2; + unsigned int is_hda_reg = 1, is_index_reg = 0; + int ret; + + if (reg > 0xffff) + is_index_reg = 1; + + mask = reg & 0xf000; + + if (is_index_reg) { /* index registers */ + val2 = reg & 0xff; + reg = reg >> 8; + nid = reg & 0xff; + ret = regmap_write(rt715->sdw_regmap, reg, 0); + if (ret < 0) + return ret; + reg2 = reg + 0x1000; + reg2 |= 0x80; + ret = regmap_write(rt715->sdw_regmap, reg2, val2); + if (ret < 0) + return ret; + + reg3 = RT715_PRIV_DATA_R_H | nid; + ret = regmap_write(rt715->sdw_regmap, reg3, + ((*val >> 8) & 0xff)); + if (ret < 0) + return ret; + reg4 = reg3 + 0x1000; + reg4 |= 0x80; + ret = regmap_write(rt715->sdw_regmap, reg4, (*val & 0xff)); + if (ret < 0) + return ret; + } else if (mask == 0x3000) { + reg += 0x8000; + ret = regmap_write(rt715->sdw_regmap, reg, *val); + if (ret < 0) + return ret; + } else if (mask == 0x7000) { + reg += 0x2000; + reg |= 0x800; + ret = regmap_write(rt715->sdw_regmap, reg, + ((*val >> 8) & 0xff)); + if (ret < 0) + return ret; + reg2 = reg + 0x1000; + reg2 |= 0x80; + ret = regmap_write(rt715->sdw_regmap, reg2, (*val & 0xff)); + if (ret < 0) + return ret; + } else if ((reg & 0xff00) == 0x8300) { /* for R channel */ + reg2 = reg - 0x1000; + reg2 &= ~0x80; + ret = regmap_write(rt715->sdw_regmap, reg2, + ((*val >> 8) & 0xff)); + if (ret < 0) + return ret; + ret = regmap_write(rt715->sdw_regmap, reg, (*val & 0xff)); + if (ret < 0) + return ret; + } else if (mask == 0x9000) { + ret = regmap_write(rt715->sdw_regmap, reg, + ((*val >> 8) & 0xff)); + if (ret < 0) + return ret; + reg2 = reg + 0x1000; + reg2 |= 0x80; + ret = regmap_write(rt715->sdw_regmap, reg2, (*val & 0xff)); + if (ret < 0) + return ret; + } else if (mask == 0xb000) { + ret = regmap_write(rt715->sdw_regmap, reg, *val); + if (ret < 0) + return ret; + } else { + ret = regmap_read(rt715->sdw_regmap, reg, val); + if (ret < 0) + return ret; + is_hda_reg = 0; + } + + if (is_hda_reg || is_index_reg) { + sdw_data_3 = 0; + sdw_data_2 = 0; + sdw_data_1 = 0; + sdw_data_0 = 0; + ret = regmap_read(rt715->sdw_regmap, RT715_READ_HDA_3, + &sdw_data_3); + if (ret < 0) + return ret; + ret = regmap_read(rt715->sdw_regmap, RT715_READ_HDA_2, + &sdw_data_2); + if (ret < 0) + return ret; + ret = regmap_read(rt715->sdw_regmap, RT715_READ_HDA_1, + &sdw_data_1); + if (ret < 0) + return ret; + ret = regmap_read(rt715->sdw_regmap, RT715_READ_HDA_0, + &sdw_data_0); + if (ret < 0) + return ret; + *val = ((sdw_data_3 & 0xff) << 24) | + ((sdw_data_2 & 0xff) << 16) | + ((sdw_data_1 & 0xff) << 8) | (sdw_data_0 & 0xff); + } + + if (is_hda_reg == 0) + dev_dbg(dev, "[%s] %04x => %08x\n", __func__, reg, *val); + else if (is_index_reg) + dev_dbg(dev, "[%s] %04x %04x %04x %04x => %08x\n", __func__, + reg, reg2, reg3, reg4, *val); + else + dev_dbg(dev, "[%s] %04x %04x => %08x\n", + __func__, reg, reg2, *val); + + return 0; +} + +static int rt715_sdw_write(void *context, unsigned int reg, unsigned int val) +{ + struct device *dev = context; + struct rt715_priv *rt715 = dev_get_drvdata(dev); + unsigned int reg2 = 0, reg3, reg4, nid, mask, val2; + unsigned int is_index_reg = 0; + int ret; + + if (reg > 0xffff) + is_index_reg = 1; + + mask = reg & 0xf000; + + if (is_index_reg) { /* index registers */ + val2 = reg & 0xff; + reg = reg >> 8; + nid = reg & 0xff; + ret = regmap_write(rt715->sdw_regmap, reg, 0); + if (ret < 0) + return ret; + reg2 = reg + 0x1000; + reg2 |= 0x80; + ret = regmap_write(rt715->sdw_regmap, reg2, val2); + if (ret < 0) + return ret; + + reg3 = RT715_PRIV_DATA_W_H | nid; + ret = regmap_write(rt715->sdw_regmap, reg3, + ((val >> 8) & 0xff)); + if (ret < 0) + return ret; + reg4 = reg3 + 0x1000; + reg4 |= 0x80; + ret = regmap_write(rt715->sdw_regmap, reg4, (val & 0xff)); + if (ret < 0) + return ret; + is_index_reg = 1; + } else if (reg < 0x4fff) { + ret = regmap_write(rt715->sdw_regmap, reg, val); + if (ret < 0) + return ret; + } else if (reg == RT715_FUNC_RESET) { + ret = regmap_write(rt715->sdw_regmap, reg, val); + if (ret < 0) + return ret; + } else if (mask == 0x7000) { + ret = regmap_write(rt715->sdw_regmap, reg, + ((val >> 8) & 0xff)); + if (ret < 0) + return ret; + reg2 = reg + 0x1000; + reg2 |= 0x80; + ret = regmap_write(rt715->sdw_regmap, reg2, (val & 0xff)); + if (ret < 0) + return ret; + } else if ((reg & 0xff00) == 0x8300) { /* for R channel */ + reg2 = reg - 0x1000; + reg2 &= ~0x80; + ret = regmap_write(rt715->sdw_regmap, reg2, + ((val >> 8) & 0xff)); + if (ret < 0) + return ret; + ret = regmap_write(rt715->sdw_regmap, reg, (val & 0xff)); + if (ret < 0) + return ret; + } + + if (reg2 == 0) + dev_dbg(dev, "[%s] %04x <= %04x\n", __func__, reg, val); + else if (is_index_reg) + dev_dbg(dev, "[%s] %04x %04x %04x %04x <= %04x %04x\n", + __func__, reg, reg2, reg3, reg4, val2, val); + else + dev_dbg(dev, "[%s] %04x %04x <= %04x\n", + __func__, reg, reg2, val); + + return 0; +} + +static const struct regmap_config rt715_regmap = { + .reg_bits = 24, + .val_bits = 32, + .readable_reg = rt715_readable_register, /* Readable registers */ + .volatile_reg = rt715_volatile_register, /* volatile register */ + .max_register = 0x752039, /* Maximum number of register */ + .reg_defaults = rt715_reg_defaults, /* Defaults */ + .num_reg_defaults = ARRAY_SIZE(rt715_reg_defaults), + .cache_type = REGCACHE_RBTREE, + .use_single_read = true, + .use_single_write = true, + .reg_read = rt715_sdw_read, + .reg_write = rt715_sdw_write, +}; + +static const struct regmap_config rt715_sdw_regmap = { + .name = "sdw", + .reg_bits = 32, /* Total register space for SDW */ + .val_bits = 8, /* Total number of bits in register */ + .max_register = 0xff01, /* Maximum number of register */ + .cache_type = REGCACHE_NONE, + .use_single_read = true, + .use_single_write = true, +}; + +int hda_to_sdw(unsigned int nid, unsigned int verb, unsigned int payload, + unsigned int *sdw_addr_h, unsigned int *sdw_data_h, + unsigned int *sdw_addr_l, unsigned int *sdw_data_l) +{ + unsigned int offset_h, offset_l, e_verb; + + if (((verb & 0xff) != 0) || verb == 0xf00) { /* 12 bits command */ + if (verb == 0x7ff) /* special case */ + offset_h = 0; + else + offset_h = 0x3000; + + if (verb & 0x800) /* get command */ + e_verb = (verb - 0xf00) | 0x80; + else /* set command */ + e_verb = (verb - 0x700); + + *sdw_data_h = payload; /* 7 bits payload */ + *sdw_addr_l = *sdw_data_l = 0; + } else { /* 4 bits command */ + if ((verb & 0x800) == 0x800) { /* read */ + offset_h = 0x9000; + offset_l = 0xa000; + } else { /* write */ + offset_h = 0x7000; + offset_l = 0x8000; + } + e_verb = verb >> 8; + *sdw_data_h = (payload >> 8); /* 16 bits payload [15:8] */ + *sdw_addr_l = (e_verb << 8) | nid | 0x80; /* 0x80: valid bit */ + *sdw_addr_l += offset_l; + *sdw_data_l = payload & 0xff; + } + + *sdw_addr_h = (e_verb << 8) | nid; + *sdw_addr_h += offset_h; + + return 0; +} +EXPORT_SYMBOL(hda_to_sdw); + +static int rt715_update_status(struct sdw_slave *slave, + enum sdw_slave_status status) +{ + struct rt715_priv *rt715 = dev_get_drvdata(&slave->dev); + + /* Update the status */ + rt715->status = status; + /* + * Perform initialization only if slave status is present and + * hw_init flag is false + */ + if (rt715->hw_init || rt715->status != SDW_SLAVE_ATTACHED) + return 0; + + /* perform I/O transfers required for Slave initialization */ + return rt715_io_init(&slave->dev, slave); +} + +static int rt715_read_prop(struct sdw_slave *slave) +{ + struct sdw_slave_prop *prop = &slave->prop; + int nval, i, num_of_ports = 1; + u32 bit; + unsigned long addr; + struct sdw_dpn_prop *dpn; + + prop->paging_support = false; + + /* first we need to allocate memory for set bits in port lists */ + prop->source_ports = 0x50;/* BITMAP: 01010000 */ + prop->sink_ports = 0x0; /* BITMAP: 00000000 */ + + nval = hweight32(prop->source_ports); + num_of_ports += nval; + prop->src_dpn_prop = devm_kcalloc(&slave->dev, nval, + sizeof(*prop->src_dpn_prop), + GFP_KERNEL); + if (!prop->src_dpn_prop) + return -ENOMEM; + + dpn = prop->src_dpn_prop; + i = 0; + addr = prop->source_ports; + for_each_set_bit(bit, &addr, 32) { + dpn[i].num = bit; + dpn[i].simple_ch_prep_sm = true; + dpn[i].ch_prep_timeout = 10; + i++; + } + + /* do this again for sink now */ + nval = hweight32(prop->sink_ports); + num_of_ports += nval; + prop->sink_dpn_prop = devm_kcalloc(&slave->dev, nval, + sizeof(*prop->sink_dpn_prop), + GFP_KERNEL); + if (!prop->sink_dpn_prop) + return -ENOMEM; + + dpn = prop->sink_dpn_prop; + i = 0; + addr = prop->sink_ports; + for_each_set_bit(bit, &addr, 32) { + dpn[i].num = bit; + dpn[i].simple_ch_prep_sm = true; + dpn[i].ch_prep_timeout = 10; + i++; + } + + /* Allocate port_ready based on num_of_ports */ + slave->port_ready = devm_kcalloc(&slave->dev, num_of_ports, + sizeof(*slave->port_ready), + GFP_KERNEL); + if (!slave->port_ready) + return -ENOMEM; + + /* Initialize completion */ + for (i = 0; i < num_of_ports; i++) + init_completion(&slave->port_ready[i]); + + /* set the timeout values */ + prop->clk_stop_timeout = 20; + + /* wake-up event */ + prop->wake_capable = 1; + + return 0; +} + +static int rt715_bus_config(struct sdw_slave *slave, + struct sdw_bus_params *params) +{ + struct rt715_priv *rt715 = dev_get_drvdata(&slave->dev); + int ret; + + memcpy(&rt715->params, params, sizeof(*params)); + + ret = rt715_clock_config(&slave->dev); + if (ret < 0) + dev_err(&slave->dev, "Invalid clk config"); + + return 0; +} + +static struct sdw_slave_ops rt715_slave_ops = { + .read_prop = rt715_read_prop, + .update_status = rt715_update_status, + .bus_config = rt715_bus_config, +}; + +static int rt715_sdw_probe(struct sdw_slave *slave, + const struct sdw_device_id *id) +{ + struct regmap *sdw_regmap, *regmap; + + /* Assign ops */ + slave->ops = &rt715_slave_ops; + + /* Regmap Initialization */ + sdw_regmap = devm_regmap_init_sdw(slave, &rt715_sdw_regmap); + if (!sdw_regmap) + return -EINVAL; + + regmap = devm_regmap_init(&slave->dev, NULL, &slave->dev, + &rt715_regmap); + if (!regmap) + return -EINVAL; + + rt715_init(&slave->dev, sdw_regmap, regmap, slave); + + return 0; +} + +static const struct sdw_device_id rt715_id[] = { + SDW_SLAVE_ENTRY(0x025d, 0x715, 0), + {}, +}; +MODULE_DEVICE_TABLE(sdw, rt715_id); + +static int rt715_dev_suspend(struct device *dev) +{ + struct rt715_priv *rt715 = dev_get_drvdata(dev); + + if (!rt715->hw_init) + return 0; + + regcache_cache_only(rt715->regmap, true); + + return 0; +} + +#define RT715_PROBE_TIMEOUT 2000 + +static int rt715_dev_resume(struct device *dev) +{ + struct sdw_slave *slave = to_sdw_slave_device(dev); + struct rt715_priv *rt715 = dev_get_drvdata(dev); + unsigned long time; + + if (!rt715->hw_init) + return 0; + + if (!slave->unattach_request) + goto regmap_sync; + + time = wait_for_completion_timeout(&slave->initialization_complete, + msecs_to_jiffies(RT715_PROBE_TIMEOUT)); + if (!time) { + dev_err(&slave->dev, "Initialization not complete, timed out\n"); + return -ETIMEDOUT; + } + +regmap_sync: + slave->unattach_request = 0; + regcache_cache_only(rt715->regmap, false); + regcache_sync_region(rt715->regmap, 0x3000, 0x8fff); + regcache_sync_region(rt715->regmap, 0x752039, 0x752039); + + return 0; +} + +static const struct dev_pm_ops rt715_pm = { + SET_SYSTEM_SLEEP_PM_OPS(rt715_dev_suspend, rt715_dev_resume) + SET_RUNTIME_PM_OPS(rt715_dev_suspend, rt715_dev_resume, NULL) +}; + +static struct sdw_driver rt715_sdw_driver = { + .driver = { + .name = "rt715", + .owner = THIS_MODULE, + .pm = &rt715_pm, + }, + .probe = rt715_sdw_probe, + .ops = &rt715_slave_ops, + .id_table = rt715_id, +}; +module_sdw_driver(rt715_sdw_driver); + +MODULE_DESCRIPTION("ASoC RT715 driver SDW"); +MODULE_AUTHOR("Jack Yu "); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/codecs/rt715-sdw.h b/sound/soc/codecs/rt715-sdw.h new file mode 100644 index 00000000000000..0bc0efada86296 --- /dev/null +++ b/sound/soc/codecs/rt715-sdw.h @@ -0,0 +1,276 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * rt715-sdw.h -- RT715 ALSA SoC audio driver header + * + * Copyright(c) 2019 Realtek Semiconductor Corp. + */ + +#ifndef __RT715_SDW_H__ +#define __RT715_SDW_H__ + +static const struct reg_default rt715_reg_defaults[] = { + { 0x0000, 0x00 }, + { 0x0001, 0x00 }, + { 0x0002, 0x00 }, + { 0x0003, 0x00 }, + { 0x0004, 0x00 }, + { 0x0005, 0x01 }, + { 0x0020, 0x00 }, + { 0x0022, 0x00 }, + { 0x0023, 0x00 }, + { 0x0024, 0x00 }, + { 0x0025, 0x00 }, + { 0x0026, 0x00 }, + { 0x0030, 0x00 }, + { 0x0032, 0x00 }, + { 0x0033, 0x00 }, + { 0x0034, 0x00 }, + { 0x0035, 0x00 }, + { 0x0036, 0x00 }, + { 0x0040, 0x00 }, + { 0x0041, 0x00 }, + { 0x0042, 0x00 }, + { 0x0043, 0x00 }, + { 0x0044, 0x20 }, + { 0x0045, 0x01 }, + { 0x0046, 0x00 }, + { 0x0050, 0x20 }, + { 0x0051, 0x02 }, + { 0x0052, 0x5d }, + { 0x0053, 0x07 }, + { 0x0054, 0x15 }, + { 0x0055, 0x00 }, + { 0x0060, 0x00 }, + { 0x0070, 0x00 }, + { 0x0080, 0x00 }, + { 0x00e0, 0x00 }, + { 0x00e1, 0x00 }, + { 0x00e2, 0x00 }, + { 0x00e3, 0x00 }, + { 0x00e4, 0x00 }, + { 0x00e5, 0x00 }, + { 0x00ee, 0x00 }, + { 0x00ef, 0x00 }, + { 0x00f0, 0x00 }, + { 0x00f1, 0x00 }, + { 0x00f2, 0x00 }, + { 0x00f3, 0x00 }, + { 0x00f4, 0x00 }, + { 0x00f5, 0x00 }, + { 0x00fe, 0x00 }, + { 0x00ff, 0x00 }, + { 0x0200, 0x00 }, + { 0x0201, 0x00 }, + { 0x0202, 0x20 }, + { 0x0203, 0x00 }, + { 0x0204, 0x00 }, + { 0x0205, 0x00 }, + { 0x0220, 0x00 }, + { 0x0221, 0x00 }, + { 0x0222, 0x00 }, + { 0x0223, 0x00 }, + { 0x0224, 0x00 }, + { 0x0225, 0x00 }, + { 0x0226, 0x00 }, + { 0x0227, 0x00 }, + { 0x0230, 0x00 }, + { 0x0231, 0x00 }, + { 0x0232, 0x00 }, + { 0x0233, 0x00 }, + { 0x0234, 0x00 }, + { 0x0235, 0x00 }, + { 0x0236, 0x00 }, + { 0x0237, 0x00 }, + { 0x02e0, 0x00 }, + { 0x02f0, 0x00 }, + { 0x0400, 0x00 }, + { 0x0401, 0x00 }, + { 0x0402, 0x20 }, + { 0x0403, 0x00 }, + { 0x0404, 0x00 }, + { 0x0405, 0x00 }, + { 0x0420, 0x00 }, + { 0x0421, 0x00 }, + { 0x0422, 0x00 }, + { 0x0423, 0x00 }, + { 0x0424, 0x00 }, + { 0x0425, 0x00 }, + { 0x0426, 0x00 }, + { 0x0427, 0x00 }, + { 0x0430, 0x00 }, + { 0x0431, 0x00 }, + { 0x0432, 0x00 }, + { 0x0433, 0x00 }, + { 0x0434, 0x00 }, + { 0x0435, 0x00 }, + { 0x0436, 0x00 }, + { 0x0437, 0x00 }, + { 0x04e0, 0x00 }, + { 0x04f0, 0x00 }, + { 0x0600, 0x00 }, + { 0x0601, 0x00 }, + { 0x0602, 0x20 }, + { 0x0603, 0x00 }, + { 0x0604, 0x00 }, + { 0x0605, 0x00 }, + { 0x0620, 0x00 }, + { 0x0621, 0x00 }, + { 0x0622, 0x00 }, + { 0x0623, 0x00 }, + { 0x0624, 0x00 }, + { 0x0625, 0x00 }, + { 0x0626, 0x00 }, + { 0x0627, 0x00 }, + { 0x0630, 0x00 }, + { 0x0631, 0x00 }, + { 0x0632, 0x00 }, + { 0x0633, 0x00 }, + { 0x0634, 0x00 }, + { 0x0635, 0x00 }, + { 0x0636, 0x00 }, + { 0x0637, 0x00 }, + { 0x06e0, 0x00 }, + { 0x06f0, 0x00 }, + { 0x0f00, 0x00 }, + { 0x0f01, 0x00 }, + { 0x0f02, 0x00 }, + { 0x0f03, 0x00 }, + { 0x0f04, 0x00 }, + { 0x0f05, 0xff }, + { 0x0f06, 0x00 }, + { 0x0f07, 0x00 }, + { 0x0f08, 0x00 }, + { 0x0f09, 0x00 }, + { 0x0f0a, 0x00 }, + { 0x0f0b, 0x00 }, + { 0x0f0c, 0x00 }, + { 0x0f0d, 0x00 }, + { 0x0f0e, 0x00 }, + { 0x0f0f, 0x00 }, + { 0x0f10, 0x00 }, + { 0x0f11, 0x00 }, + { 0x0f12, 0x00 }, + { 0x0f13, 0x00 }, + { 0x0f14, 0x00 }, + { 0x0f15, 0xff }, + { 0x0f16, 0x00 }, + { 0x0f17, 0x00 }, + { 0x0f18, 0x00 }, + { 0x0f19, 0x00 }, + { 0x0f1a, 0x00 }, + { 0x0f1b, 0x00 }, + { 0x0f1c, 0x00 }, + { 0x0f1d, 0x00 }, + { 0x0f1e, 0x00 }, + { 0x0f1f, 0x00 }, + { 0x0f20, 0x00 }, + { 0x0f21, 0x00 }, + { 0x0f22, 0x00 }, + { 0x0f23, 0x00 }, + { 0x0f24, 0x00 }, + { 0x0f25, 0x00 }, + { 0x0f26, 0x00 }, + { 0x0f27, 0x00 }, + { 0x0f30, 0x00 }, + { 0x0f31, 0x00 }, + { 0x0f32, 0x00 }, + { 0x0f33, 0x00 }, + { 0x0f34, 0x00 }, + { 0x0f35, 0x00 }, + { 0x0f36, 0x00 }, + { 0x0f37, 0x00 }, + { 0x2000, 0x00 }, + { 0x2001, 0x00 }, + { 0x2002, 0x00 }, + { 0x2003, 0x00 }, + { 0x2004, 0x00 }, + { 0x2005, 0x00 }, + { 0x2006, 0x00 }, + { 0x2007, 0x00 }, + { 0x2008, 0x00 }, + { 0x2009, 0x03 }, + { 0x200a, 0x00 }, + { 0x200b, 0x00 }, + { 0x200c, 0x00 }, + { 0x200d, 0x00 }, + { 0x200e, 0x00 }, + { 0x200f, 0x00 }, + { 0x2010, 0x00 }, + { 0x2011, 0x00 }, + { 0x2012, 0x00 }, + { 0x2013, 0x00 }, + { 0x2014, 0x00 }, + { 0x2015, 0x00 }, + { 0x2016, 0x00 }, + { 0x201a, 0x00 }, + { 0x201b, 0x00 }, + { 0x201c, 0x00 }, + { 0x201d, 0x00 }, + { 0x201e, 0x00 }, + { 0x201f, 0x00 }, + { 0x2020, 0x00 }, + { 0x2021, 0x00 }, + { 0x2022, 0x00 }, + { 0x2023, 0x00 }, + { 0x2024, 0x00 }, + { 0x2025, 0x01 }, + { 0x2026, 0x00 }, + { 0x2027, 0x00 }, + { 0x2029, 0x00 }, + { 0x202a, 0x00 }, + { 0x202d, 0x00 }, + { 0x202e, 0x00 }, + { 0x202f, 0x00 }, + { 0x2030, 0x00 }, + { 0x2031, 0x00 }, + { 0x2032, 0x00 }, + { 0x2033, 0x00 }, + { 0x2034, 0x00 }, + { 0x2200, 0x00 }, + { 0x2201, 0x00 }, + { 0x2202, 0x00 }, + { 0x2203, 0x00 }, + { 0x2204, 0x00 }, + { 0x2206, 0x00 }, + { 0x2207, 0x00 }, + { 0x2208, 0x00 }, + { 0x2209, 0x00 }, + { 0x220a, 0x00 }, + { 0x220b, 0x00 }, + { 0x220c, 0x00 }, + { 0x220d, 0x00 }, + { 0x220e, 0x00 }, + { 0x220f, 0x00 }, + { 0x2210, 0x00 }, + { 0x2211, 0x00 }, + { 0x2212, 0x00 }, + { 0x2220, 0x00 }, + { 0x2221, 0x00 }, + { 0x2222, 0x00 }, + { 0x2223, 0x00 }, + { 0x2230, 0x00 }, + { 0x2231, 0x0f }, + { 0x2232, 0x00 }, + { 0x2233, 0x00 }, + { 0x2234, 0x00 }, + { 0x2235, 0x00 }, + { 0x2236, 0x00 }, + { 0x2237, 0x00 }, + { 0x2238, 0x00 }, + { 0x2239, 0x00 }, + { 0x22f0, 0x00 }, + { 0x22f1, 0x00 }, + { 0x22f2, 0x00 }, + { 0x22f3, 0x00 }, + { 0x7307, 0x97 }, + { 0x8387, 0x97 }, + { 0x7308, 0x97 }, + { 0x8388, 0x97 }, + { 0x7309, 0x97 }, + { 0x8389, 0x97 }, + { 0x7327, 0x97 }, + { 0x83a7, 0x97 }, + { 0x752039, 0xa500 }, +}; + +#endif /* __RT715_H__ */ diff --git a/sound/soc/codecs/rt715.c b/sound/soc/codecs/rt715.c new file mode 100644 index 00000000000000..40e006b0fc6138 --- /dev/null +++ b/sound/soc/codecs/rt715.c @@ -0,0 +1,886 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * rt715.c -- rt715 ALSA SoC audio driver + * + * Copyright(c) 2019 Realtek Semiconductor Corp. + * + * ALC715 ASoC Codec Driver based Intel Dummy SdW codec driver + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "rt715.h" + +static int rt715_index_write(struct regmap *regmap, unsigned int reg, + unsigned int value) +{ + int ret; + unsigned int addr = ((RT715_PRIV_INDEX_W_H) << 8) | reg; + + ret = regmap_write(regmap, addr, value); + if (ret < 0) { + pr_err("Failed to set private value: %08x <= %04x %d\n", ret, + addr, value); + } + + return ret; +} + +static unsigned int rt715_index_read(struct regmap *regmap, unsigned int reg, + unsigned int *value) +{ + int ret; + unsigned int addr = ((RT715_PRIV_INDEX_W_H) << 8) | reg; + + *value = 0; + ret = regmap_read(regmap, addr, value); + if (ret < 0) { + pr_err("Failed to get private value: %08x => %04x %d\n", ret, + addr, *value); + } + + return ret; +} + +static void rt715_get_gain(struct rt715_priv *rt715, unsigned int addr_h, + unsigned int addr_l, unsigned int val_h, + unsigned int *r_val, unsigned int *l_val) +{ + int ret; + /* R Channel */ + *r_val = (val_h << 8); + ret = regmap_read(rt715->regmap, addr_l, r_val); + if (ret < 0) + pr_err("Failed to get R channel gain.\n"); + + /* L Channel */ + val_h |= 0x20; + *l_val = (val_h << 8); + ret = regmap_read(rt715->regmap, addr_h, l_val); + if (ret < 0) + pr_err("Failed to get L channel gain.\n"); +} + +/* For Verb-Set Amplifier Gain (Verb ID = 3h) */ +static int rt715_set_amp_gain_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct snd_soc_dapm_context *dapm = + snd_soc_component_get_dapm(component); + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + struct rt715_priv *rt715 = snd_soc_component_get_drvdata(component); + unsigned int addr_h, addr_l, val_h, val_ll, val_lr; + unsigned int read_ll, read_rl; + int i; + + /* Can't use update bit function, so read the original value first */ + addr_h = mc->reg; + addr_l = mc->rreg; + if (mc->shift == RT715_DIR_OUT_SFT) /* output */ + val_h = 0x80; + else /* input */ + val_h = 0x0; + + rt715_get_gain(rt715, addr_h, addr_l, val_h, &read_rl, &read_ll); + + /* L Channel */ + if (mc->invert) { + /* for mute */ + val_ll = (mc->max - ucontrol->value.integer.value[0]) << 7; + /* keep gain */ + read_ll = read_ll & 0x7f; + val_ll |= read_ll; + } else { + /* for gain */ + val_ll = ((ucontrol->value.integer.value[0]) & 0x7f); + if (val_ll > mc->max) + val_ll = mc->max; + /* keep mute status */ + read_ll = read_ll & 0x80; + val_ll |= read_ll; + } + + /* R Channel */ + if (mc->invert) { + regmap_write(rt715->regmap, + RT715_SET_AUDIO_POWER_STATE, AC_PWRST_D0); + /* for mute */ + val_lr = (mc->max - ucontrol->value.integer.value[1]) << 7; + /* keep gain */ + read_rl = read_rl & 0x7f; + val_lr |= read_rl; + } else { + /* for gain */ + val_lr = ((ucontrol->value.integer.value[1]) & 0x7f); + if (val_lr > mc->max) + val_lr = mc->max; + /* keep mute status */ + read_rl = read_rl & 0x80; + val_lr |= read_rl; + } + + for (i = 0; i < 3; i++) { /* retry 3 times at most */ + + if (val_ll == val_lr) { + /* Set both L/R channels at the same time */ + val_h = (1 << mc->shift) | (3 << 4); + regmap_write(rt715->regmap, addr_h, + (val_h << 8 | val_ll)); + regmap_write(rt715->regmap, addr_l, + (val_h << 8 | val_ll)); + } else { + /* Lch*/ + val_h = (1 << mc->shift) | (1 << 5); + regmap_write(rt715->regmap, addr_h, + (val_h << 8 | val_ll)); + /* Rch */ + val_h = (1 << mc->shift) | (1 << 4); + regmap_write(rt715->regmap, addr_l, + (val_h << 8 | val_lr)); + } + /* check result */ + if (mc->shift == RT715_DIR_OUT_SFT) /* output */ + val_h = 0x80; + else /* input */ + val_h = 0x0; + + rt715_get_gain(rt715, addr_h, addr_l, val_h, + &read_rl, &read_ll); + if (read_rl == val_lr && read_ll == val_ll) + break; + } + /* D0:power on state, D3: power saving mode */ + if (dapm->bias_level <= SND_SOC_BIAS_STANDBY) + regmap_write(rt715->regmap, + RT715_SET_AUDIO_POWER_STATE, AC_PWRST_D3); + return 0; +} + +static int rt715_set_amp_gain_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct rt715_priv *rt715 = snd_soc_component_get_drvdata(component); + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + unsigned int addr_h, addr_l, val_h; + unsigned int read_ll, read_rl; + + addr_h = mc->reg; + addr_l = mc->rreg; + if (mc->shift == RT715_DIR_OUT_SFT) /* output */ + val_h = 0x80; + else /* input */ + val_h = 0x0; + + rt715_get_gain(rt715, addr_h, addr_l, val_h, &read_rl, &read_ll); + + if (mc->invert) { + /* for mute status */ + read_ll = !((read_ll & 0x80) >> RT715_MUTE_SFT); + read_rl = !((read_rl & 0x80) >> RT715_MUTE_SFT); + } else { + /* for gain */ + read_ll = read_ll & 0x7f; + read_rl = read_rl & 0x7f; + } + ucontrol->value.integer.value[0] = read_ll; + ucontrol->value.integer.value[1] = read_rl; + + return 0; +} + +static const DECLARE_TLV_DB_SCALE(out_vol_tlv, -6525, 75, 0); +static const DECLARE_TLV_DB_SCALE(in_vol_tlv, -1725, 75, 0); +static const DECLARE_TLV_DB_SCALE(mic_vol_tlv, 0, 1000, 0); + +#define SOC_DOUBLE_R_EXT(xname, reg_left, reg_right, xshift, xmax, xinvert,\ + xhandler_get, xhandler_put) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = (xname), \ + .info = snd_soc_info_volsw, \ + .get = xhandler_get, .put = xhandler_put, \ + .private_value = SOC_DOUBLE_R_VALUE(reg_left, reg_right, xshift, \ + xmax, xinvert) } + +static const struct snd_kcontrol_new rt715_snd_controls[] = { + /* Capture switch */ + SOC_DOUBLE_R_EXT("ADC 07 Capture Switch", RT715_SET_GAIN_MIC_ADC_H, + RT715_SET_GAIN_MIC_ADC_L, RT715_DIR_IN_SFT, 1, 1, + rt715_set_amp_gain_get, rt715_set_amp_gain_put), + SOC_DOUBLE_R_EXT("ADC 08 Capture Switch", RT715_SET_GAIN_LINE_ADC_H, + RT715_SET_GAIN_LINE_ADC_L, RT715_DIR_IN_SFT, 1, 1, + rt715_set_amp_gain_get, rt715_set_amp_gain_put), + SOC_DOUBLE_R_EXT("ADC 09 Capture Switch", RT715_SET_GAIN_MIX_ADC_H, + RT715_SET_GAIN_MIX_ADC_L, RT715_DIR_IN_SFT, 1, 1, + rt715_set_amp_gain_get, rt715_set_amp_gain_put), + SOC_DOUBLE_R_EXT("ADC 27 Capture Switch", RT715_SET_GAIN_MIX_ADC2_H, + RT715_SET_GAIN_MIX_ADC2_L, RT715_DIR_IN_SFT, 1, 1, + rt715_set_amp_gain_get, rt715_set_amp_gain_put), + /* Volume Control */ + SOC_DOUBLE_R_EXT_TLV("ADC 07 Capture Volume", RT715_SET_GAIN_MIC_ADC_H, + RT715_SET_GAIN_MIC_ADC_L, RT715_DIR_IN_SFT, 0x3f, 0, + rt715_set_amp_gain_get, rt715_set_amp_gain_put, + in_vol_tlv), + SOC_DOUBLE_R_EXT_TLV("ADC 08 Capture Volume", RT715_SET_GAIN_LINE_ADC_H, + RT715_SET_GAIN_LINE_ADC_L, RT715_DIR_IN_SFT, 0x3f, 0, + rt715_set_amp_gain_get, rt715_set_amp_gain_put, + in_vol_tlv), + SOC_DOUBLE_R_EXT_TLV("ADC 09 Capture Volume", RT715_SET_GAIN_MIX_ADC_H, + RT715_SET_GAIN_MIX_ADC_L, RT715_DIR_IN_SFT, 0x3f, 0, + rt715_set_amp_gain_get, rt715_set_amp_gain_put, + in_vol_tlv), + SOC_DOUBLE_R_EXT_TLV("ADC 27 Capture Volume", RT715_SET_GAIN_MIX_ADC2_H, + RT715_SET_GAIN_MIX_ADC2_L, RT715_DIR_IN_SFT, 0x3f, 0, + rt715_set_amp_gain_get, rt715_set_amp_gain_put, + in_vol_tlv), + /* MIC Boost Control */ + SOC_DOUBLE_R_EXT_TLV("DMIC1 Boost", RT715_SET_GAIN_DMIC1_H, + RT715_SET_GAIN_DMIC1_L, RT715_DIR_IN_SFT, 3, 0, + rt715_set_amp_gain_get, rt715_set_amp_gain_put, + mic_vol_tlv), + SOC_DOUBLE_R_EXT_TLV("DMIC2 Boost", RT715_SET_GAIN_DMIC2_H, + RT715_SET_GAIN_DMIC2_L, RT715_DIR_IN_SFT, 3, 0, + rt715_set_amp_gain_get, rt715_set_amp_gain_put, + mic_vol_tlv), + SOC_DOUBLE_R_EXT_TLV("DMIC3 Boost", RT715_SET_GAIN_DMIC3_H, + RT715_SET_GAIN_DMIC3_L, RT715_DIR_IN_SFT, 3, 0, + rt715_set_amp_gain_get, rt715_set_amp_gain_put, + mic_vol_tlv), + SOC_DOUBLE_R_EXT_TLV("DMIC4 Boost", RT715_SET_GAIN_DMIC4_H, + RT715_SET_GAIN_DMIC4_L, RT715_DIR_IN_SFT, 3, 0, + rt715_set_amp_gain_get, rt715_set_amp_gain_put, + mic_vol_tlv), + SOC_DOUBLE_R_EXT_TLV("MIC1 Boost", RT715_SET_GAIN_MIC1_H, + RT715_SET_GAIN_MIC1_L, RT715_DIR_IN_SFT, 3, 0, + rt715_set_amp_gain_get, rt715_set_amp_gain_put, + mic_vol_tlv), + SOC_DOUBLE_R_EXT_TLV("MIC2 Boost", RT715_SET_GAIN_MIC2_H, + RT715_SET_GAIN_MIC2_L, RT715_DIR_IN_SFT, 3, 0, + rt715_set_amp_gain_get, rt715_set_amp_gain_put, + mic_vol_tlv), + SOC_DOUBLE_R_EXT_TLV("LINE1 Boost", RT715_SET_GAIN_LINE1_H, + RT715_SET_GAIN_LINE1_L, RT715_DIR_IN_SFT, 3, 0, + rt715_set_amp_gain_get, rt715_set_amp_gain_put, + mic_vol_tlv), + SOC_DOUBLE_R_EXT_TLV("LINE2 Boost", RT715_SET_GAIN_LINE2_H, + RT715_SET_GAIN_LINE2_L, RT715_DIR_IN_SFT, 3, 0, + rt715_set_amp_gain_get, rt715_set_amp_gain_put, + mic_vol_tlv), +}; + +static int rt715_mux_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = + snd_soc_dapm_kcontrol_component(kcontrol); + struct rt715_priv *rt715 = snd_soc_component_get_drvdata(component); + struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; + unsigned int reg, val; + int ret; + + /* nid = e->reg, vid = 0xf01 */ + reg = RT715_VERB_SET_CONNECT_SEL | e->reg; + ret = regmap_read(rt715->regmap, reg, &val); + if (ret < 0) { + dev_err(component->dev, "%s: sdw read failed: %d\n", + __func__, ret); + return ret; + } + + /* + * The first two indices of ADC Mux 24/25 are routed to the same + * hardware source. ie, ADC Mux 24 0/1 will both connect to MIC2. + * To have a unique set of inputs, we skip the index1 of the muxes. + */ + if ((e->reg == RT715_MUX_IN3 || e->reg == RT715_MUX_IN4) && (val > 0)) + val -= 1; + ucontrol->value.enumerated.item[0] = val; + + return 0; +} + +static int rt715_mux_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = + snd_soc_dapm_kcontrol_component(kcontrol); + struct snd_soc_dapm_context *dapm = + snd_soc_dapm_kcontrol_dapm(kcontrol); + struct rt715_priv *rt715 = snd_soc_component_get_drvdata(component); + struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; + unsigned int *item = ucontrol->value.enumerated.item; + unsigned int val, val2 = 0, change, reg; + int ret; + + if (item[0] >= e->items) + return -EINVAL; + + /* Verb ID = 0x701h, nid = e->reg */ + val = snd_soc_enum_item_to_val(e, item[0]) << e->shift_l; + + reg = RT715_VERB_SET_CONNECT_SEL | e->reg; + ret = regmap_read(rt715->regmap, reg, &val2); + if (ret < 0) { + dev_err(component->dev, "%s: sdw read failed: %d\n", + __func__, ret); + return ret; + } + + if (val == val2) + change = 0; + else + change = 1; + + if (change) { + reg = RT715_VERB_SET_CONNECT_SEL | e->reg; + regmap_write(rt715->regmap, reg, val); + } + + snd_soc_dapm_mux_update_power(dapm, kcontrol, + item[0], e, NULL); + + return change; +} + +static const char * const adc_22_23_mux_text[] = { + "MIC1", + "MIC2", + "LINE1", + "LINE2", + "DMIC1", + "DMIC2", + "DMIC3", + "DMIC4", +}; + +/** + * Due to mux design for nid 24 (MUX_IN3)/25 (MUX_IN4), connection index 0 and + * 1 will be connected to the same dmic source, therefore we skip index 1 to + * avoid misunderstanding on usage of dapm routing. + */ +static const unsigned int rt715_adc_24_25_values[] = { + 0, + 2, + 3, + 4, + 5, +}; + +static const char * const adc_24_mux_text[] = { + "MIC2", + "DMIC1", + "DMIC2", + "DMIC3", + "DMIC4", +}; + +static const char * const adc_25_mux_text[] = { + "MIC1", + "DMIC1", + "DMIC2", + "DMIC3", + "DMIC4", +}; + +static SOC_ENUM_SINGLE_DECL( + rt715_adc22_enum, RT715_MUX_IN1, 0, adc_22_23_mux_text); + +static SOC_ENUM_SINGLE_DECL( + rt715_adc23_enum, RT715_MUX_IN2, 0, adc_22_23_mux_text); + +static SOC_VALUE_ENUM_SINGLE_DECL(rt715_adc24_enum, + RT715_MUX_IN3, 0, 0xf, + adc_24_mux_text, rt715_adc_24_25_values); +static SOC_VALUE_ENUM_SINGLE_DECL(rt715_adc25_enum, + RT715_MUX_IN4, 0, 0xf, + adc_25_mux_text, rt715_adc_24_25_values); + +static const struct snd_kcontrol_new rt715_adc22_mux = + SOC_DAPM_ENUM_EXT("ADC 22 Mux", rt715_adc22_enum, + rt715_mux_get, rt715_mux_put); + +static const struct snd_kcontrol_new rt715_adc23_mux = + SOC_DAPM_ENUM_EXT("ADC 23 Mux", rt715_adc23_enum, + rt715_mux_get, rt715_mux_put); + +static const struct snd_kcontrol_new rt715_adc24_mux = + SOC_DAPM_ENUM_EXT("ADC 24 Mux", rt715_adc24_enum, + rt715_mux_get, rt715_mux_put); + +static const struct snd_kcontrol_new rt715_adc25_mux = + SOC_DAPM_ENUM_EXT("ADC 25 Mux", rt715_adc25_enum, + rt715_mux_get, rt715_mux_put); + +static const struct snd_soc_dapm_widget rt715_dapm_widgets[] = { + SND_SOC_DAPM_INPUT("DMIC1"), + SND_SOC_DAPM_INPUT("DMIC2"), + SND_SOC_DAPM_INPUT("DMIC3"), + SND_SOC_DAPM_INPUT("DMIC4"), + SND_SOC_DAPM_INPUT("MIC1"), + SND_SOC_DAPM_INPUT("MIC2"), + SND_SOC_DAPM_INPUT("LINE1"), + SND_SOC_DAPM_INPUT("LINE2"), + SND_SOC_DAPM_ADC("ADC 07", NULL, RT715_SET_STREAMID_MIC_ADC, 4, 0), + SND_SOC_DAPM_ADC("ADC 08", NULL, RT715_SET_STREAMID_LINE_ADC, 4, 0), + SND_SOC_DAPM_ADC("ADC 09", NULL, RT715_SET_STREAMID_MIX_ADC, 4, 0), + SND_SOC_DAPM_ADC("ADC 27", NULL, RT715_SET_STREAMID_MIX_ADC2, 4, 0), + SND_SOC_DAPM_MUX("ADC 22 Mux", SND_SOC_NOPM, 0, 0, + &rt715_adc22_mux), + SND_SOC_DAPM_MUX("ADC 23 Mux", SND_SOC_NOPM, 0, 0, + &rt715_adc23_mux), + SND_SOC_DAPM_MUX("ADC 24 Mux", SND_SOC_NOPM, 0, 0, + &rt715_adc24_mux), + SND_SOC_DAPM_MUX("ADC 25 Mux", SND_SOC_NOPM, 0, 0, + &rt715_adc25_mux), + SND_SOC_DAPM_AIF_OUT("DP4TX", "DP4 Capture", 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("DP6TX", "DP6 Capture", 0, SND_SOC_NOPM, 0, 0), +}; + +static const struct snd_soc_dapm_route rt715_audio_map[] = { + {"DP6TX", NULL, "ADC 09"}, + {"DP6TX", NULL, "ADC 08"}, + {"DP4TX", NULL, "ADC 07"}, + {"DP4TX", NULL, "ADC 27"}, + {"ADC 09", NULL, "ADC 22 Mux"}, + {"ADC 08", NULL, "ADC 23 Mux"}, + {"ADC 07", NULL, "ADC 24 Mux"}, + {"ADC 27", NULL, "ADC 25 Mux"}, + {"ADC 22 Mux", "MIC1", "MIC1"}, + {"ADC 22 Mux", "MIC2", "MIC2"}, + {"ADC 22 Mux", "LINE1", "LINE1"}, + {"ADC 22 Mux", "LINE2", "LINE2"}, + {"ADC 22 Mux", "DMIC1", "DMIC1"}, + {"ADC 22 Mux", "DMIC2", "DMIC2"}, + {"ADC 22 Mux", "DMIC3", "DMIC3"}, + {"ADC 22 Mux", "DMIC4", "DMIC4"}, + {"ADC 23 Mux", "MIC1", "MIC1"}, + {"ADC 23 Mux", "MIC2", "MIC2"}, + {"ADC 23 Mux", "LINE1", "LINE1"}, + {"ADC 23 Mux", "LINE2", "LINE2"}, + {"ADC 23 Mux", "DMIC1", "DMIC1"}, + {"ADC 23 Mux", "DMIC2", "DMIC2"}, + {"ADC 23 Mux", "DMIC3", "DMIC3"}, + {"ADC 23 Mux", "DMIC4", "DMIC4"}, + {"ADC 24 Mux", "MIC2", "MIC2"}, + {"ADC 24 Mux", "DMIC1", "DMIC1"}, + {"ADC 24 Mux", "DMIC2", "DMIC2"}, + {"ADC 24 Mux", "DMIC3", "DMIC3"}, + {"ADC 24 Mux", "DMIC4", "DMIC4"}, + {"ADC 25 Mux", "MIC1", "MIC1"}, + {"ADC 25 Mux", "DMIC1", "DMIC1"}, + {"ADC 25 Mux", "DMIC2", "DMIC2"}, + {"ADC 25 Mux", "DMIC3", "DMIC3"}, + {"ADC 25 Mux", "DMIC4", "DMIC4"}, +}; + +static int rt715_set_bias_level(struct snd_soc_component *component, + enum snd_soc_bias_level level) +{ + struct snd_soc_dapm_context *dapm = + snd_soc_component_get_dapm(component); + struct rt715_priv *rt715 = snd_soc_component_get_drvdata(component); + + switch (level) { + case SND_SOC_BIAS_PREPARE: + if (dapm->bias_level == SND_SOC_BIAS_STANDBY) { + regmap_write(rt715->regmap, + RT715_SET_AUDIO_POWER_STATE, + AC_PWRST_D0); + } + break; + + case SND_SOC_BIAS_STANDBY: + regmap_write(rt715->regmap, + RT715_SET_AUDIO_POWER_STATE, + AC_PWRST_D3); + break; + + default: + break; + } + dapm->bias_level = level; + return 0; +} + +static const struct snd_soc_component_driver soc_codec_dev_rt715 = { + .set_bias_level = rt715_set_bias_level, + .controls = rt715_snd_controls, + .num_controls = ARRAY_SIZE(rt715_snd_controls), + .dapm_widgets = rt715_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(rt715_dapm_widgets), + .dapm_routes = rt715_audio_map, + .num_dapm_routes = ARRAY_SIZE(rt715_audio_map), +}; + +static int rt715_set_sdw_stream(struct snd_soc_dai *dai, void *sdw_stream, + int direction) +{ + + struct sdw_stream_data *stream; + + stream = kzalloc(sizeof(*stream), GFP_KERNEL); + if (!stream) + return -ENOMEM; + + stream->sdw_stream = (struct sdw_stream_runtime *)sdw_stream; + + /* Use tx_mask or rx_mask to configure stream tag and set dma_data */ + if (direction == SNDRV_PCM_STREAM_PLAYBACK) + dai->playback_dma_data = stream; + else + dai->capture_dma_data = stream; + + return 0; +} + +static void rt715_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) + +{ + struct sdw_stream_data *stream; + + stream = snd_soc_dai_get_dma_data(dai, substream); + snd_soc_dai_set_dma_data(dai, substream, NULL); + kfree(stream); +} + +static int rt715_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct rt715_priv *rt715 = snd_soc_component_get_drvdata(component); + struct sdw_stream_config stream_config; + struct sdw_port_config port_config; + enum sdw_data_direction direction; + struct sdw_stream_data *stream; + int retval, port, num_channels; + unsigned int val = 0; + + stream = snd_soc_dai_get_dma_data(dai, substream); + + if (!stream) + return -EINVAL; + + if (!rt715->slave) + return -EINVAL; + + switch (dai->id) { + case RT715_AIF1: + direction = SDW_DATA_DIR_TX; + port = 6; + rt715_index_write(rt715->regmap, RT715_SDW_INPUT_SEL, 0xa500); + break; + case RT715_AIF2: + direction = SDW_DATA_DIR_TX; + port = 4; + rt715_index_write(rt715->regmap, RT715_SDW_INPUT_SEL, 0xa000); + break; + default: + dev_err(component->dev, "Invalid DAI id %d\n", dai->id); + return -EINVAL; + } + + stream_config.frame_rate = params_rate(params); + stream_config.ch_count = params_channels(params); + stream_config.bps = snd_pcm_format_width(params_format(params)); + stream_config.direction = direction; + + num_channels = params_channels(params); + port_config.ch_mask = (1 << (num_channels)) - 1; + port_config.num = port; + + retval = sdw_stream_add_slave(rt715->slave, &stream_config, + &port_config, 1, stream->sdw_stream); + if (retval) { + dev_err(dai->dev, "Unable to configure port\n"); + return retval; + } + + switch (params_rate(params)) { + /* bit 14 0:48K 1:44.1K */ + /* bit 15 Stream Type 0:PCM 1:Non-PCM, should always be PCM */ + case 44100: + val |= 0x40 << 8; + break; + case 48000: + val |= 0x0 << 8; + break; + default: + dev_err(component->dev, "Unsupported sample rate %d\n", + params_rate(params)); + return -EINVAL; + } + + if (params_channels(params) <= 16) { + /* bit 3:0 Number of Channel */ + val |= (params_channels(params) - 1); + } else { + dev_err(component->dev, "Unsupported channels %d\n", + params_channels(params)); + return -EINVAL; + } + + switch (params_width(params)) { + /* bit 6:4 Bits per Sample */ + case 8: + break; + case 16: + val |= (0x1 << 4); + break; + case 20: + val |= (0x2 << 4); + break; + case 24: + val |= (0x3 << 4); + break; + case 32: + val |= (0x4 << 4); + break; + default: + return -EINVAL; + } + + regmap_write(rt715->regmap, RT715_MIC_ADC_FORMAT_H, val); + regmap_write(rt715->regmap, RT715_MIC_LINE_FORMAT_H, val); + regmap_write(rt715->regmap, RT715_MIX_ADC_FORMAT_H, val); + regmap_write(rt715->regmap, RT715_MIX_ADC2_FORMAT_H, val); + + return retval; +} + +static int rt715_pcm_hw_free(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct rt715_priv *rt715 = snd_soc_component_get_drvdata(component); + struct sdw_stream_data *stream = + snd_soc_dai_get_dma_data(dai, substream); + + if (!rt715->slave) + return -EINVAL; + + sdw_stream_remove_slave(rt715->slave, stream->sdw_stream); + return 0; +} + +#define RT715_STEREO_RATES (SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000) +#define RT715_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \ + SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S8) + +static struct snd_soc_dai_ops rt715_ops = { + .hw_params = rt715_pcm_hw_params, + .hw_free = rt715_pcm_hw_free, + .set_sdw_stream = rt715_set_sdw_stream, + .shutdown = rt715_shutdown, +}; + +static struct snd_soc_dai_driver rt715_dai[] = { + { + .name = "rt715-aif1", + .id = RT715_AIF1, + .capture = { + .stream_name = "DP6 Capture", + .channels_min = 1, + .channels_max = 2, + .rates = RT715_STEREO_RATES, + .formats = RT715_FORMATS, + }, + .ops = &rt715_ops, + }, + { + .name = "rt715-aif2", + .id = RT715_AIF2, + .capture = { + .stream_name = "DP4 Capture", + .channels_min = 1, + .channels_max = 2, + .rates = RT715_STEREO_RATES, + .formats = RT715_FORMATS, + }, + .ops = &rt715_ops, + }, +}; + +/* Bus clock frequency */ +#define RT715_CLK_FREQ_9600000HZ 9600000 +#define RT715_CLK_FREQ_12000000HZ 12000000 +#define RT715_CLK_FREQ_6000000HZ 6000000 +#define RT715_CLK_FREQ_4800000HZ 4800000 +#define RT715_CLK_FREQ_2400000HZ 2400000 +#define RT715_CLK_FREQ_12288000HZ 12288000 + +int rt715_clock_config(struct device *dev) +{ + struct rt715_priv *rt715 = dev_get_drvdata(dev); + unsigned int clk_freq, value; + + clk_freq = (rt715->params.curr_dr_freq >> 1); + + switch (clk_freq) { + case RT715_CLK_FREQ_12000000HZ: + value = 0x0; + break; + case RT715_CLK_FREQ_6000000HZ: + value = 0x1; + break; + case RT715_CLK_FREQ_9600000HZ: + value = 0x2; + break; + case RT715_CLK_FREQ_4800000HZ: + value = 0x3; + break; + case RT715_CLK_FREQ_2400000HZ: + value = 0x4; + break; + case RT715_CLK_FREQ_12288000HZ: + value = 0x5; + break; + default: + return -EINVAL; + } + + regmap_write(rt715->regmap, 0xe0, value); + regmap_write(rt715->regmap, 0xf0, value); + + return 0; +} + +int rt715_init(struct device *dev, struct regmap *sdw_regmap, + struct regmap *regmap, struct sdw_slave *slave) +{ + struct rt715_priv *rt715; + int ret; + + rt715 = devm_kzalloc(dev, sizeof(*rt715), GFP_KERNEL); + if (!rt715) + return -ENOMEM; + + dev_set_drvdata(dev, rt715); + rt715->slave = slave; + rt715->regmap = regmap; + rt715->sdw_regmap = sdw_regmap; + + /* + * Mark hw_init to false + * HW init will be performed when device reports present + */ + rt715->hw_init = false; + rt715->first_init = false; + + ret = devm_snd_soc_register_component(dev, + &soc_codec_dev_rt715, + rt715_dai, + ARRAY_SIZE(rt715_dai)); + + return ret; +} + +int rt715_io_init(struct device *dev, struct sdw_slave *slave) +{ + struct rt715_priv *rt715 = dev_get_drvdata(dev); + + if (rt715->hw_init) + return 0; + + /* + * PM runtime is only enabled when a Slave reports as Attached + */ + if (!rt715->first_init) { + /* set autosuspend parameters */ + pm_runtime_set_autosuspend_delay(&slave->dev, 3000); + pm_runtime_use_autosuspend(&slave->dev); + + /* update count of parent 'active' children */ + pm_runtime_set_active(&slave->dev); + + /* make sure the device does not suspend immediately */ + pm_runtime_mark_last_busy(&slave->dev); + + pm_runtime_enable(&slave->dev); + + rt715->first_init = true; + } + + pm_runtime_get_noresume(&slave->dev); + + /* Mute nid=08h/09h */ + regmap_write(rt715->regmap, RT715_SET_GAIN_LINE_ADC_H, 0xb080); + regmap_write(rt715->regmap, RT715_SET_GAIN_MIX_ADC_H, 0xb080); + /* Mute nid=07h/27h */ + regmap_write(rt715->regmap, RT715_SET_GAIN_MIC_ADC_H, 0xb080); + regmap_write(rt715->regmap, RT715_SET_GAIN_MIX_ADC2_H, 0xb080); + + /* Set Pin Widget */ + regmap_write(rt715->regmap, RT715_SET_PIN_DMIC1, 0x20); + regmap_write(rt715->regmap, RT715_SET_PIN_DMIC2, 0x20); + regmap_write(rt715->regmap, RT715_SET_PIN_DMIC3, 0x20); + regmap_write(rt715->regmap, RT715_SET_PIN_DMIC4, 0x20); + /* Set Converter Stream */ + regmap_write(rt715->regmap, RT715_SET_STREAMID_LINE_ADC, 0x10); + regmap_write(rt715->regmap, RT715_SET_STREAMID_MIX_ADC, 0x10); + regmap_write(rt715->regmap, RT715_SET_STREAMID_MIC_ADC, 0x10); + regmap_write(rt715->regmap, RT715_SET_STREAMID_MIX_ADC2, 0x10); + /* Set Configuration Default */ + regmap_write(rt715->regmap, RT715_SET_DMIC1_CONFIG_DEFAULT1, 0xd0); + regmap_write(rt715->regmap, RT715_SET_DMIC1_CONFIG_DEFAULT2, 0x11); + regmap_write(rt715->regmap, RT715_SET_DMIC1_CONFIG_DEFAULT3, 0xa1); + regmap_write(rt715->regmap, RT715_SET_DMIC1_CONFIG_DEFAULT4, 0x81); + regmap_write(rt715->regmap, RT715_SET_DMIC2_CONFIG_DEFAULT1, 0xd1); + regmap_write(rt715->regmap, RT715_SET_DMIC2_CONFIG_DEFAULT2, 0x11); + regmap_write(rt715->regmap, RT715_SET_DMIC2_CONFIG_DEFAULT3, 0xa1); + regmap_write(rt715->regmap, RT715_SET_DMIC2_CONFIG_DEFAULT4, 0x81); + regmap_write(rt715->regmap, RT715_SET_DMIC3_CONFIG_DEFAULT1, 0xd0); + regmap_write(rt715->regmap, RT715_SET_DMIC3_CONFIG_DEFAULT2, 0x11); + regmap_write(rt715->regmap, RT715_SET_DMIC3_CONFIG_DEFAULT3, 0xa1); + regmap_write(rt715->regmap, RT715_SET_DMIC3_CONFIG_DEFAULT4, 0x81); + regmap_write(rt715->regmap, RT715_SET_DMIC4_CONFIG_DEFAULT1, 0xd1); + regmap_write(rt715->regmap, RT715_SET_DMIC4_CONFIG_DEFAULT2, 0x11); + regmap_write(rt715->regmap, RT715_SET_DMIC4_CONFIG_DEFAULT3, 0xa1); + regmap_write(rt715->regmap, RT715_SET_DMIC4_CONFIG_DEFAULT4, 0x81); + + /* Finish Initial Settings, set power to D3 */ + regmap_write(rt715->regmap, RT715_SET_AUDIO_POWER_STATE, AC_PWRST_D3); + + /* Mark Slave initialization complete */ + rt715->hw_init = true; + + pm_runtime_mark_last_busy(&slave->dev); + pm_runtime_put_autosuspend(&slave->dev); + + return 0; +} + +MODULE_DESCRIPTION("ASoC rt715 driver"); +MODULE_DESCRIPTION("ASoC rt715 driver SDW"); +MODULE_AUTHOR("Jack Yu "); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/codecs/rt715.h b/sound/soc/codecs/rt715.h new file mode 100644 index 00000000000000..52d24cfb581d1b --- /dev/null +++ b/sound/soc/codecs/rt715.h @@ -0,0 +1,221 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * rt715.h -- RT715 ALSA SoC audio driver header + * + * Copyright(c) 2019 Realtek Semiconductor Corp. + */ + +#ifndef __RT715_H__ +#define __RT715_H__ + +#include + +struct rt715_priv { + struct regmap *regmap; + struct regmap *sdw_regmap; + struct snd_soc_codec *codec; + struct sdw_slave *slave; + int dbg_nid; + int dbg_vid; + int dbg_payload; + enum sdw_slave_status status; + struct sdw_bus_params params; + bool hw_init; + bool first_init; +}; + +struct sdw_stream_data { + struct sdw_stream_runtime *sdw_stream; +}; + +/* NID */ +#define RT715_AUDIO_FUNCTION_GROUP 0x01 +#define RT715_MIC_ADC 0x07 +#define RT715_LINE_ADC 0x08 +#define RT715_MIX_ADC 0x09 +#define RT715_DMIC1 0x12 +#define RT715_DMIC2 0x13 +#define RT715_MIC1 0x18 +#define RT715_MIC2 0x19 +#define RT715_LINE1 0x1a +#define RT715_LINE2 0x1b +#define RT715_DMIC3 0x1d +#define RT715_DMIC4 0x29 +#define RT715_VENDOR_REGISTERS 0x20 +#define RT715_MUX_IN1 0x22 +#define RT715_MUX_IN2 0x23 +#define RT715_MUX_IN3 0x24 +#define RT715_MUX_IN4 0x25 +#define RT715_MIX_ADC2 0x27 +#define RT715_INLINE_CMD 0x55 + +/* Index (NID:20h) */ +#define RT715_SDW_INPUT_SEL 0x39 +#define RT715_EXT_DMIC_CLK_CTRL2 0x54 + +/* Verb */ +#define RT715_VERB_SET_CONNECT_SEL 0x3100 +#define RT715_VERB_GET_CONNECT_SEL 0xb100 +#define RT715_VERB_SET_EAPD_BTLENABLE 0x3c00 +#define RT715_VERB_SET_POWER_STATE 0x3500 +#define RT715_VERB_SET_CHANNEL_STREAMID 0x3600 +#define RT715_VERB_SET_PIN_WIDGET_CONTROL 0x3700 +#define RT715_VERB_SET_CONFIG_DEFAULT1 0x4c00 +#define RT715_VERB_SET_CONFIG_DEFAULT2 0x4d00 +#define RT715_VERB_SET_CONFIG_DEFAULT3 0x4e00 +#define RT715_VERB_SET_CONFIG_DEFAULT4 0x4f00 +#define RT715_VERB_SET_UNSOLICITED_ENABLE 0x3800 +#define RT715_SET_AMP_GAIN_MUTE_H 0x7300 +#define RT715_SET_AMP_GAIN_MUTE_L 0x8380 +#define RT715_READ_HDA_3 0x2012 +#define RT715_READ_HDA_2 0x2013 +#define RT715_READ_HDA_1 0x2014 +#define RT715_READ_HDA_0 0x2015 +#define RT715_PRIV_INDEX_W_H 0x7520 +#define RT715_PRIV_INDEX_W_L 0x85a0 +#define RT715_PRIV_DATA_W_H 0x7420 +#define RT715_PRIV_DATA_W_L 0x84a0 +#define RT715_PRIV_INDEX_R_H 0x9d20 +#define RT715_PRIV_INDEX_R_L 0xada0 +#define RT715_PRIV_DATA_R_H 0x9c20 +#define RT715_PRIV_DATA_R_L 0xaca0 +#define RT715_MIC_ADC_FORMAT_H 0x7207 +#define RT715_MIC_ADC_FORMAT_L 0x8287 +#define RT715_MIC_LINE_FORMAT_H 0x7208 +#define RT715_MIC_LINE_FORMAT_L 0x8288 +#define RT715_MIX_ADC_FORMAT_H 0x7209 +#define RT715_MIX_ADC_FORMAT_L 0x8289 +#define RT715_MIX_ADC2_FORMAT_H 0x7227 +#define RT715_MIX_ADC2_FORMAT_L 0x82a7 +#define RT715_FUNC_RESET 0xff01 + +#define RT715_SET_AUDIO_POWER_STATE\ + (RT715_VERB_SET_POWER_STATE | RT715_AUDIO_FUNCTION_GROUP) +#define RT715_SET_PIN_DMIC1\ + (RT715_VERB_SET_PIN_WIDGET_CONTROL | RT715_DMIC1) +#define RT715_SET_PIN_DMIC2\ + (RT715_VERB_SET_PIN_WIDGET_CONTROL | RT715_DMIC2) +#define RT715_SET_PIN_DMIC3\ + (RT715_VERB_SET_PIN_WIDGET_CONTROL | RT715_DMIC3) +#define RT715_SET_PIN_DMIC4\ + (RT715_VERB_SET_PIN_WIDGET_CONTROL | RT715_DMIC4) +#define RT715_SET_PIN_MIC1\ + (RT715_VERB_SET_PIN_WIDGET_CONTROL | RT715_MIC1) +#define RT715_SET_PIN_MIC2\ + (RT715_VERB_SET_PIN_WIDGET_CONTROL | RT715_MIC2) +#define RT715_SET_PIN_LINE1\ + (RT715_VERB_SET_PIN_WIDGET_CONTROL | RT715_LINE1) +#define RT715_SET_PIN_LINE2\ + (RT715_VERB_SET_PIN_WIDGET_CONTROL | RT715_LINE2) +#define RT715_SET_MIC1_UNSOLICITED_ENABLE\ + (RT715_VERB_SET_UNSOLICITED_ENABLE | RT715_MIC1) +#define RT715_SET_MIC2_UNSOLICITED_ENABLE\ + (RT715_VERB_SET_UNSOLICITED_ENABLE | RT715_MIC2) +#define RT715_SET_STREAMID_MIC_ADC\ + (RT715_VERB_SET_CHANNEL_STREAMID | RT715_MIC_ADC) +#define RT715_SET_STREAMID_LINE_ADC\ + (RT715_VERB_SET_CHANNEL_STREAMID | RT715_LINE_ADC) +#define RT715_SET_STREAMID_MIX_ADC\ + (RT715_VERB_SET_CHANNEL_STREAMID | RT715_MIX_ADC) +#define RT715_SET_STREAMID_MIX_ADC2\ + (RT715_VERB_SET_CHANNEL_STREAMID | RT715_MIX_ADC2) +#define RT715_SET_GAIN_MIC_ADC_L\ + (RT715_SET_AMP_GAIN_MUTE_L | RT715_MIC_ADC) +#define RT715_SET_GAIN_MIC_ADC_H\ + (RT715_SET_AMP_GAIN_MUTE_H | RT715_MIC_ADC) +#define RT715_SET_GAIN_LINE_ADC_L\ + (RT715_SET_AMP_GAIN_MUTE_L | RT715_LINE_ADC) +#define RT715_SET_GAIN_LINE_ADC_H\ + (RT715_SET_AMP_GAIN_MUTE_H | RT715_LINE_ADC) +#define RT715_SET_GAIN_MIX_ADC_L\ + (RT715_SET_AMP_GAIN_MUTE_L | RT715_MIX_ADC) +#define RT715_SET_GAIN_MIX_ADC_H\ + (RT715_SET_AMP_GAIN_MUTE_H | RT715_MIX_ADC) +#define RT715_SET_GAIN_MIX_ADC2_L\ + (RT715_SET_AMP_GAIN_MUTE_L | RT715_MIX_ADC2) +#define RT715_SET_GAIN_MIX_ADC2_H\ + (RT715_SET_AMP_GAIN_MUTE_H | RT715_MIX_ADC2) +#define RT715_SET_GAIN_DMIC1_L\ + (RT715_SET_AMP_GAIN_MUTE_L | RT715_DMIC1) +#define RT715_SET_GAIN_DMIC1_H\ + (RT715_SET_AMP_GAIN_MUTE_H | RT715_DMIC1) +#define RT715_SET_GAIN_DMIC2_L\ + (RT715_SET_AMP_GAIN_MUTE_L | RT715_DMIC2) +#define RT715_SET_GAIN_DMIC2_H\ + (RT715_SET_AMP_GAIN_MUTE_H | RT715_DMIC2) +#define RT715_SET_GAIN_DMIC3_L\ + (RT715_SET_AMP_GAIN_MUTE_L | RT715_DMIC3) +#define RT715_SET_GAIN_DMIC3_H\ + (RT715_SET_AMP_GAIN_MUTE_H | RT715_DMIC3) +#define RT715_SET_GAIN_DMIC4_L\ + (RT715_SET_AMP_GAIN_MUTE_L | RT715_DMIC4) +#define RT715_SET_GAIN_DMIC4_H\ + (RT715_SET_AMP_GAIN_MUTE_H | RT715_DMIC4) +#define RT715_SET_GAIN_MIC1_L\ + (RT715_SET_AMP_GAIN_MUTE_L | RT715_MIC1) +#define RT715_SET_GAIN_MIC1_H\ + (RT715_SET_AMP_GAIN_MUTE_H | RT715_MIC1) +#define RT715_SET_GAIN_MIC2_L\ + (RT715_SET_AMP_GAIN_MUTE_L | RT715_MIC2) +#define RT715_SET_GAIN_MIC2_H\ + (RT715_SET_AMP_GAIN_MUTE_H | RT715_MIC2) +#define RT715_SET_GAIN_LINE1_L\ + (RT715_SET_AMP_GAIN_MUTE_L | RT715_LINE1) +#define RT715_SET_GAIN_LINE1_H\ + (RT715_SET_AMP_GAIN_MUTE_H | RT715_LINE1) +#define RT715_SET_GAIN_LINE2_L\ + (RT715_SET_AMP_GAIN_MUTE_L | RT715_LINE2) +#define RT715_SET_GAIN_LINE2_H\ + (RT715_SET_AMP_GAIN_MUTE_H | RT715_LINE2) +#define RT715_SET_DMIC1_CONFIG_DEFAULT1\ + (RT715_VERB_SET_CONFIG_DEFAULT1 | RT715_DMIC1) +#define RT715_SET_DMIC2_CONFIG_DEFAULT1\ + (RT715_VERB_SET_CONFIG_DEFAULT1 | RT715_DMIC2) +#define RT715_SET_DMIC1_CONFIG_DEFAULT2\ + (RT715_VERB_SET_CONFIG_DEFAULT2 | RT715_DMIC1) +#define RT715_SET_DMIC2_CONFIG_DEFAULT2\ + (RT715_VERB_SET_CONFIG_DEFAULT2 | RT715_DMIC2) +#define RT715_SET_DMIC1_CONFIG_DEFAULT3\ + (RT715_VERB_SET_CONFIG_DEFAULT3 | RT715_DMIC1) +#define RT715_SET_DMIC2_CONFIG_DEFAULT3\ + (RT715_VERB_SET_CONFIG_DEFAULT3 | RT715_DMIC2) +#define RT715_SET_DMIC1_CONFIG_DEFAULT4\ + (RT715_VERB_SET_CONFIG_DEFAULT4 | RT715_DMIC1) +#define RT715_SET_DMIC2_CONFIG_DEFAULT4\ + (RT715_VERB_SET_CONFIG_DEFAULT4 | RT715_DMIC2) +#define RT715_SET_DMIC3_CONFIG_DEFAULT1\ + (RT715_VERB_SET_CONFIG_DEFAULT1 | RT715_DMIC3) +#define RT715_SET_DMIC4_CONFIG_DEFAULT1\ + (RT715_VERB_SET_CONFIG_DEFAULT1 | RT715_DMIC4) +#define RT715_SET_DMIC3_CONFIG_DEFAULT2\ + (RT715_VERB_SET_CONFIG_DEFAULT2 | RT715_DMIC3) +#define RT715_SET_DMIC4_CONFIG_DEFAULT2\ + (RT715_VERB_SET_CONFIG_DEFAULT2 | RT715_DMIC4) +#define RT715_SET_DMIC3_CONFIG_DEFAULT3\ + (RT715_VERB_SET_CONFIG_DEFAULT3 | RT715_DMIC3) +#define RT715_SET_DMIC4_CONFIG_DEFAULT3\ + (RT715_VERB_SET_CONFIG_DEFAULT3 | RT715_DMIC4) +#define RT715_SET_DMIC3_CONFIG_DEFAULT4\ + (RT715_VERB_SET_CONFIG_DEFAULT4 | RT715_DMIC3) +#define RT715_SET_DMIC4_CONFIG_DEFAULT4\ + (RT715_VERB_SET_CONFIG_DEFAULT4 | RT715_DMIC4) + +#define RT715_MUTE_SFT 7 +#define RT715_DIR_IN_SFT 6 +#define RT715_DIR_OUT_SFT 7 + +enum { + RT715_AIF1, + RT715_AIF2, + RT715_AIFS, +}; + +int rt715_io_init(struct device *dev, struct sdw_slave *slave); +int rt715_init(struct device *dev, struct regmap *sdw_regmap, + struct regmap *regmap, struct sdw_slave *slave); + +int hda_to_sdw(unsigned int nid, unsigned int verb, unsigned int payload, + unsigned int *sdw_addr_h, unsigned int *sdw_data_h, + unsigned int *sdw_addr_l, unsigned int *sdw_data_l); +int rt715_clock_config(struct device *dev); +#endif /* __RT715_H__ */ diff --git a/sound/soc/intel/boards/Kconfig b/sound/soc/intel/boards/Kconfig index 7815080e5e7eb1..b8472a5c928b14 100644 --- a/sound/soc/intel/boards/Kconfig +++ b/sound/soc/intel/boards/Kconfig @@ -481,21 +481,58 @@ config SND_SOC_INTEL_SOF_CML_RT1011_RT5682_MACH endif ## SND_SOC_SOF_COMETLAKE_LP && SND_SOC_SOF_HDA_LINK -if SND_SOC_SOF_TIGERLAKE +if SND_SOC_SOF_TIGERLAKE && SND_SOC_SOF_INTEL_SOUNDWIRE -config SND_SOC_INTEL_TGL_RT1308_MACH - tristate "TGL with RT1308 in I2S Mode" +config SND_SOC_INTEL_TGL_RT711_RT1308_MACH + tristate "TGL with RT711 in SDW mode and RT1308 in I2S Mode" depends on I2C && ACPI depends on MFD_INTEL_LPSS || COMPILE_TEST + select SND_SOC_RT711_SDW select SND_SOC_RT1308 select SND_SOC_DMIC select SND_SOC_HDAC_HDMI if SND_SOC_SOF_HDA_LINK help This adds support for ASoC machine driver for Tigerlake platforms - with RT1308 I2S audio codec. + with SoundWire RT711 and RT1308 I2S audio codec. Say Y if you have such a device. If unsure select "N". -endif ## SND_SOC_SOF_TIGERLAKE +endif ## SND_SOC_SOF_TIGERLAKE && SND_SOC_SOF_INTEL_SOUNDWIRE + +if SND_SOC_SOF_INTEL_SOUNDWIRE + +config SND_SOC_INTEL_SOUNDWIRE_RT700_MACH + tristate "SoundWire with RT700 codec" + depends on SOUNDWIRE && ACPI + select SND_SOC_RT700_SDW + select SND_SOC_DMIC + select SND_SOC_HDAC_HDMI if SND_SOC_SOF_HDA_LINK + help + Add support for Intel SoundWire-based platforms connected to RT700 + on link 0 or 1 + If unsure select "N" + +config SND_SOC_INTEL_SOUNDWIRE_RT711_MACH + tristate "SoundWire with RT711 codec" + depends on SOUNDWIRE && ACPI + select SND_SOC_RT711_SDW + help + Add support for Intel SoundWire-based platforms connected to RT711 + on link 0 or 1 + If unsure select "N" + +config SND_SOC_INTEL_SOUNDWIRE_RT711_RT1308_RT715_MACH + tristate "SoundWire with RT711, RT1308 and RT715" + depends on SOUNDWIRE && ACPI + select SND_SOC_RT711_SDW + select SND_SOC_RT1308_SDW + select SND_SOC_RT715_SDW + select SND_SOC_HDAC_HDMI if SND_SOC_SOF_HDA_LINK + help + Add support for Intel SoundWire-based platforms connected to RT711, + RT1308 and RT715 + If unsure select "N". + +endif endif ## SND_SOC_INTEL_MACH diff --git a/sound/soc/intel/boards/Makefile b/sound/soc/intel/boards/Makefile index 2c2eaaf7e83e4d..fe56cab93104d4 100644 --- a/sound/soc/intel/boards/Makefile +++ b/sound/soc/intel/boards/Makefile @@ -1,3 +1,5 @@ +ccflags-y += -DDEBUG + # SPDX-License-Identifier: GPL-2.0 snd-soc-sst-haswell-objs := haswell.o snd-soc-sst-byt-rt5640-mach-objs := byt-rt5640.o @@ -30,8 +32,10 @@ snd-soc-skl_rt286-objs := skl_rt286.o snd-soc-skl_hda_dsp-objs := skl_hda_dsp_generic.o skl_hda_dsp_common.o hda_dsp_common.o snd-skl_nau88l25_max98357a-objs := skl_nau88l25_max98357a.o snd-soc-skl_nau88l25_ssm4567-objs := skl_nau88l25_ssm4567.o -snd-soc-tgl-rt1308-objs := tgl_rt1308.o hda_dsp_common.o - +snd-soc-tgl-rt711-rt1308-objs := sdw_rt711_i2s_rt1308.o hda_dsp_common.o +snd-soc-sdw-rt700-objs := sdw_rt700.o hda_dsp_common.o +snd-soc-sdw-rt711-objs := sdw_rt711.o hda_dsp_common.o +snd-soc-sdw-rt711-rt1308-rt715-objs := sdw_rt711_rt1308_rt715.o hda_dsp_common.o obj-$(CONFIG_SND_SOC_INTEL_SOF_RT5682_MACH) += snd-soc-sof_rt5682.o obj-$(CONFIG_SND_SOC_INTEL_HASWELL_MACH) += snd-soc-sst-haswell.o obj-$(CONFIG_SND_SOC_INTEL_BYT_RT5640_MACH) += snd-soc-sst-byt-rt5640-mach.o @@ -63,4 +67,7 @@ obj-$(CONFIG_SND_SOC_INTEL_SKL_RT286_MACH) += snd-soc-skl_rt286.o obj-$(CONFIG_SND_SOC_INTEL_SKL_NAU88L25_MAX98357A_MACH) += snd-skl_nau88l25_max98357a.o obj-$(CONFIG_SND_SOC_INTEL_SKL_NAU88L25_SSM4567_MACH) += snd-soc-skl_nau88l25_ssm4567.o obj-$(CONFIG_SND_SOC_INTEL_SKL_HDA_DSP_GENERIC_MACH) += snd-soc-skl_hda_dsp.o -obj-$(CONFIG_SND_SOC_INTEL_TGL_RT1308_MACH) += snd-soc-tgl-rt1308.o +obj-$(CONFIG_SND_SOC_INTEL_TGL_RT711_RT1308_MACH) += snd-soc-tgl-rt711-rt1308.o +obj-$(CONFIG_SND_SOC_INTEL_SOUNDWIRE_RT700_MACH) += snd-soc-sdw-rt700.o +obj-$(CONFIG_SND_SOC_INTEL_SOUNDWIRE_RT711_MACH) += snd-soc-sdw-rt711.o +obj-$(CONFIG_SND_SOC_INTEL_SOUNDWIRE_RT711_RT1308_RT715_MACH) += snd-soc-sdw-rt711-rt1308-rt715.o diff --git a/sound/soc/intel/boards/sdw_rt700.c b/sound/soc/intel/boards/sdw_rt700.c new file mode 100644 index 00000000000000..fd46f0b18b149e --- /dev/null +++ b/sound/soc/intel/boards/sdw_rt700.c @@ -0,0 +1,319 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (c) 2016-19 Intel Corporation + +/* + * sdw_rt700 - ASOC Machine driver for Intel SoundWire platforms + * connected to ALC700 device + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "../../codecs/hdac_hdmi.h" +#include "hda_dsp_common.h" + +struct mc_private { + struct list_head hdmi_pcm_list; + bool common_hdmi_codec_drv; +}; + +#if IS_ENABLED(CONFIG_SND_SOC_HDAC_HDMI) +static struct snd_soc_jack hdmi[3]; + +struct hdmi_pcm { + struct list_head head; + struct snd_soc_dai *codec_dai; + int device; +}; + +static int hdmi_init(struct snd_soc_pcm_runtime *rtd) +{ + struct mc_private *ctx = snd_soc_card_get_drvdata(rtd->card); + struct snd_soc_dai *dai = rtd->codec_dai; + struct hdmi_pcm *pcm; + + pcm = devm_kzalloc(rtd->card->dev, sizeof(*pcm), GFP_KERNEL); + if (!pcm) + return -ENOMEM; + + /* dai_link id is 1:1 mapped to the PCM device */ + pcm->device = rtd->dai_link->id; + pcm->codec_dai = dai; + + list_add_tail(&pcm->head, &ctx->hdmi_pcm_list); + + return 0; +} + +#define NAME_SIZE 32 +static int card_late_probe(struct snd_soc_card *card) +{ + struct mc_private *ctx = snd_soc_card_get_drvdata(card); + struct hdmi_pcm *pcm; + struct snd_soc_component *component = NULL; + int err, i = 0; + char jack_name[NAME_SIZE]; + + pcm = list_first_entry(&ctx->hdmi_pcm_list, struct hdmi_pcm, + head); + component = pcm->codec_dai->component; + + if (ctx->common_hdmi_codec_drv) + return hda_dsp_hdmi_build_controls(card, component); + + list_for_each_entry(pcm, &ctx->hdmi_pcm_list, head) { + component = pcm->codec_dai->component; + snprintf(jack_name, sizeof(jack_name), + "HDMI/DP, pcm=%d Jack", pcm->device); + err = snd_soc_card_jack_new(card, jack_name, + SND_JACK_AVOUT, &hdmi[i], + NULL, 0); + + if (err) + return err; + + err = hdac_hdmi_jack_init(pcm->codec_dai, pcm->device, + &hdmi[i]); + if (err < 0) + return err; + + i++; + } + + if (!component) + return -EINVAL; + + return hdac_hdmi_jack_port_init(component, &card->dapm); +} +#else +static int card_late_probe(struct snd_soc_card *card) +{ + return 0; +} +#endif + +static const struct snd_soc_dapm_widget widgets[] = { + SND_SOC_DAPM_HP("Headphones", NULL), + SND_SOC_DAPM_MIC("AMIC", NULL), + SND_SOC_DAPM_SPK("Speaker", NULL), +}; + +static const struct snd_soc_dapm_route map[] = { + /*Headphones*/ + { "Headphones", NULL, "HP" }, + { "Speaker", NULL, "SPK" }, + { "MIC2", NULL, "AMIC" }, +}; + +static const struct snd_kcontrol_new controls[] = { + SOC_DAPM_PIN_SWITCH("Headphones"), + SOC_DAPM_PIN_SWITCH("AMIC"), + SOC_DAPM_PIN_SWITCH("Speaker"), +}; + +SND_SOC_DAILINK_DEF(sdw0_pin2, + DAILINK_COMP_ARRAY(COMP_CPU("SDW0 Pin2"))); +SND_SOC_DAILINK_DEF(sdw0_pin3, + DAILINK_COMP_ARRAY(COMP_CPU("SDW0 Pin3"))); +SND_SOC_DAILINK_DEF(sdw0_codec, + DAILINK_COMP_ARRAY(COMP_CODEC("sdw:0:25d:700:0", "rt700-aif1"))); + +SND_SOC_DAILINK_DEF(sdw1_pin2, + DAILINK_COMP_ARRAY(COMP_CPU("SDW1 Pin2"))); +SND_SOC_DAILINK_DEF(sdw1_pin3, + DAILINK_COMP_ARRAY(COMP_CPU("SDW1 Pin3"))); +SND_SOC_DAILINK_DEF(sdw1_codec, + DAILINK_COMP_ARRAY(COMP_CODEC("sdw:1:25d:700:0", "rt700-aif1"))); + +SND_SOC_DAILINK_DEF(dmic_pin, + DAILINK_COMP_ARRAY(COMP_CPU("DMIC01 Pin"))); + +SND_SOC_DAILINK_DEF(dmic16k_pin, + DAILINK_COMP_ARRAY(COMP_CPU("DMIC16k Pin"))); + +SND_SOC_DAILINK_DEF(dmic_codec, + DAILINK_COMP_ARRAY(COMP_CODEC("dmic-codec", "dmic-hifi"))); + +#if IS_ENABLED(CONFIG_SND_SOC_HDAC_HDMI) +SND_SOC_DAILINK_DEF(idisp1_pin, + DAILINK_COMP_ARRAY(COMP_CPU("iDisp1 Pin"))); +SND_SOC_DAILINK_DEF(idisp1_codec, + DAILINK_COMP_ARRAY(COMP_CODEC("ehdaudio0D2", "intel-hdmi-hifi1"))); + +SND_SOC_DAILINK_DEF(idisp2_pin, + DAILINK_COMP_ARRAY(COMP_CPU("iDisp2 Pin"))); +SND_SOC_DAILINK_DEF(idisp2_codec, + DAILINK_COMP_ARRAY(COMP_CODEC("ehdaudio0D2", "intel-hdmi-hifi2"))); + +SND_SOC_DAILINK_DEF(idisp3_pin, + DAILINK_COMP_ARRAY(COMP_CPU("iDisp3 Pin"))); +SND_SOC_DAILINK_DEF(idisp3_codec, + DAILINK_COMP_ARRAY(COMP_CODEC("ehdaudio0D2", "intel-hdmi-hifi3"))); +#endif + +SND_SOC_DAILINK_DEF(platform, + DAILINK_COMP_ARRAY(COMP_PLATFORM("0000:00:1f.3"))); + +struct snd_soc_dai_link dailink[] = { + { + .name = "SDW0-Playback", + .id = 0, + .no_pcm = 1, + .dpcm_playback = 1, + .nonatomic = true, + SND_SOC_DAILINK_REG(sdw0_pin2, sdw0_codec, platform), + }, + { + .name = "SDW0-Capture", + .id = 1, + .no_pcm = 1, + .dpcm_capture = 1, + .nonatomic = true, + SND_SOC_DAILINK_REG(sdw0_pin3, sdw0_codec, platform), + }, + { + .name = "dmic01", + .id = 2, + .ignore_suspend = 1, + .dpcm_capture = 1, + .no_pcm = 1, + SND_SOC_DAILINK_REG(dmic_pin, dmic_codec, platform), + }, + { + .name = "dmic16k", + .id = 3, + .ignore_suspend = 1, + .dpcm_capture = 1, + .no_pcm = 1, + SND_SOC_DAILINK_REG(dmic16k_pin, dmic_codec, platform), + }, +#if IS_ENABLED(CONFIG_SND_SOC_HDAC_HDMI) + { + .name = "iDisp1", + .id = 4, + .init = hdmi_init, + .no_pcm = 1, + .dpcm_playback = 1, + SND_SOC_DAILINK_REG(idisp1_pin, idisp1_codec, platform), + }, + { + .name = "iDisp2", + .id = 5, + .init = hdmi_init, + .no_pcm = 1, + .dpcm_playback = 1, + SND_SOC_DAILINK_REG(idisp2_pin, idisp2_codec, platform), + }, + { + .name = "iDisp3", + .id = 6, + .init = hdmi_init, + .no_pcm = 1, + .dpcm_playback = 1, + SND_SOC_DAILINK_REG(idisp3_pin, idisp3_codec, platform), + }, +#endif +}; + +/* SoC card */ +static struct snd_soc_card card_sdw_rt700 = { + .name = "sdw-rt700", + .dai_link = dailink, + .num_links = ARRAY_SIZE(dailink), + .controls = controls, + .num_controls = ARRAY_SIZE(controls), + .dapm_widgets = widgets, + .num_dapm_widgets = ARRAY_SIZE(widgets), + .dapm_routes = map, + .num_dapm_routes = ARRAY_SIZE(map), + .late_probe = card_late_probe, +}; + +static int mc_probe(struct platform_device *pdev) +{ + struct mc_private *ctx; + struct snd_soc_acpi_mach *mach; + const char *platform_name; + struct snd_soc_card *card = &card_sdw_rt700; + const char *board; + int ret; + + dev_dbg(&pdev->dev, "Entry %s\n", __func__); + + ctx = devm_kzalloc(&pdev->dev, sizeof(*ctx), GFP_KERNEL); + if (!ctx) + return -ENOMEM; + +#if IS_ENABLED(CONFIG_SND_SOC_HDAC_HDMI) + INIT_LIST_HEAD(&ctx->hdmi_pcm_list); +#endif + + board = dmi_get_system_info(DMI_BOARD_NAME); + if (strstr(board, "CometLake U DDR4 HR")) { + dailink[0].name = "SDW1-Playback"; + dailink[0].codecs = sdw1_codec; + dailink[0].num_codecs = ARRAY_SIZE(sdw1_codec); + dailink[0].cpus = sdw1_pin2; + dailink[0].num_cpus = ARRAY_SIZE(sdw1_pin2); + + dailink[1].name = "SDW1-Capture"; + dailink[1].codecs = sdw1_codec; + dailink[1].num_codecs = ARRAY_SIZE(sdw1_codec); + dailink[1].cpus = sdw1_pin3; + dailink[1].num_cpus = ARRAY_SIZE(sdw1_pin3); + } + + card->dev = &pdev->dev; + + /* override platform name, if required */ + mach = (&pdev->dev)->platform_data; + platform_name = mach->mach_params.platform; + + ret = snd_soc_fixup_dai_links_platform_name(card, platform_name); + if (ret) + return ret; + + ctx->common_hdmi_codec_drv = mach->mach_params.common_hdmi_codec_drv; + + snd_soc_card_set_drvdata(card, ctx); + + /* Register the card */ + ret = devm_snd_soc_register_card(&pdev->dev, card); + if (ret) { + dev_err(card->dev, "snd_soc_register_card failed %d\n", ret); + return ret; + } + + platform_set_drvdata(pdev, card); + + return ret; +} + +static struct platform_driver sdw_rt700_driver = { + .driver = { + .name = "sdw_rt700", + .pm = &snd_soc_pm_ops, + }, + .probe = mc_probe, +}; + +module_platform_driver(sdw_rt700_driver); + +MODULE_DESCRIPTION("ASoC SoundWire RT700 Machine driver"); +MODULE_AUTHOR("Bard Liao "); +MODULE_AUTHOR("Pierre-Louis Bossart "); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:sdw_rt700"); diff --git a/sound/soc/intel/boards/sdw_rt711.c b/sound/soc/intel/boards/sdw_rt711.c new file mode 100644 index 00000000000000..09da46b150992f --- /dev/null +++ b/sound/soc/intel/boards/sdw_rt711.c @@ -0,0 +1,276 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (c) 2019 Intel Corporation + +/* + * sdw_rt711 - ASOC Machine driver for Intel SoundWire platforms + * connected to ALC711 device + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "../../codecs/hdac_hdmi.h" +#include "hda_dsp_common.h" + +struct mc_private { + struct list_head hdmi_pcm_list; + bool common_hdmi_codec_drv; + struct snd_soc_jack sdw_headset; +}; + +#if IS_ENABLED(CONFIG_SND_SOC_HDAC_HDMI) +static struct snd_soc_jack hdmi[3]; + +struct hdmi_pcm { + struct list_head head; + struct snd_soc_dai *codec_dai; + int device; +}; + +static int hdmi_init(struct snd_soc_pcm_runtime *rtd) +{ + struct mc_private *ctx = snd_soc_card_get_drvdata(rtd->card); + struct snd_soc_dai *dai = rtd->codec_dai; + struct hdmi_pcm *pcm; + + pcm = devm_kzalloc(rtd->card->dev, sizeof(*pcm), GFP_KERNEL); + if (!pcm) + return -ENOMEM; + + /* dai_link id is 1:1 mapped to the PCM device */ + pcm->device = rtd->dai_link->id; + pcm->codec_dai = dai; + + list_add_tail(&pcm->head, &ctx->hdmi_pcm_list); + + return 0; +} + +#define NAME_SIZE 32 +static int card_late_probe(struct snd_soc_card *card) +{ + struct mc_private *ctx = snd_soc_card_get_drvdata(card); + struct hdmi_pcm *pcm; + struct snd_soc_component *component = NULL; + int err, i = 0; + char jack_name[NAME_SIZE]; + + pcm = list_first_entry(&ctx->hdmi_pcm_list, struct hdmi_pcm, + head); + component = pcm->codec_dai->component; + + if (ctx->common_hdmi_codec_drv) + return hda_dsp_hdmi_build_controls(card, component); + + list_for_each_entry(pcm, &ctx->hdmi_pcm_list, head) { + component = pcm->codec_dai->component; + snprintf(jack_name, sizeof(jack_name), + "HDMI/DP, pcm=%d Jack", pcm->device); + err = snd_soc_card_jack_new(card, jack_name, + SND_JACK_AVOUT, &hdmi[i], + NULL, 0); + + if (err) + return err; + + err = hdac_hdmi_jack_init(pcm->codec_dai, pcm->device, + &hdmi[i]); + if (err < 0) + return err; + + i++; + } + + if (!component) + return -EINVAL; + + return hdac_hdmi_jack_port_init(component, &card->dapm); +} +#else +static int card_late_probe(struct snd_soc_card *card) +{ + return 0; +} +#endif + +static struct snd_soc_jack_pin sdw_jack_pins[] = { + { + .pin = "Headphone", + .mask = SND_JACK_HEADPHONE, + }, + { + .pin = "Headset Mic", + .mask = SND_JACK_MICROPHONE, + }, +}; + +static int headset_init(struct snd_soc_pcm_runtime *rtd) +{ + struct mc_private *ctx = snd_soc_card_get_drvdata(rtd->card); + struct snd_soc_component *component = rtd->codec_dai->component; + struct snd_soc_jack *jack; + int ret; + + ret = snd_soc_card_jack_new(rtd->card, "Headset Jack", + SND_JACK_HEADSET | SND_JACK_BTN_0 | + SND_JACK_BTN_1 | SND_JACK_BTN_2 | + SND_JACK_BTN_3, + &ctx->sdw_headset, + sdw_jack_pins, + ARRAY_SIZE(sdw_jack_pins)); + if (ret) { + dev_err(rtd->card->dev, "Headset Jack creation failed: %d\n", + ret); + return ret; + } + + jack = &ctx->sdw_headset; + + snd_jack_set_key(jack->jack, SND_JACK_BTN_0, KEY_VOLUMEUP); + snd_jack_set_key(jack->jack, SND_JACK_BTN_1, KEY_PLAYPAUSE); + snd_jack_set_key(jack->jack, SND_JACK_BTN_2, KEY_VOLUMEDOWN); + snd_jack_set_key(jack->jack, SND_JACK_BTN_3, KEY_VOICECOMMAND); + + ret = snd_soc_component_set_jack(component, jack, NULL); + + if (ret) + dev_err(rtd->card->dev, "Headset Jack call-back failed: %d\n", + ret); + + return ret; +} + +static const struct snd_soc_dapm_widget widgets[] = { + SND_SOC_DAPM_HP("Headphone", NULL), + SND_SOC_DAPM_MIC("Headset Mic", NULL), + SND_SOC_DAPM_SPK("Speaker", NULL), +}; + +static const struct snd_soc_dapm_route map[] = { + /*Headphones*/ + { "Headphone", NULL, "HP" }, + { "MIC2", NULL, "Headset Mic" }, +}; + +static const struct snd_kcontrol_new controls[] = { + SOC_DAPM_PIN_SWITCH("Headphone"), + SOC_DAPM_PIN_SWITCH("Headset Mic"), + SOC_DAPM_PIN_SWITCH("Speaker"), +}; + +SND_SOC_DAILINK_DEF(sdw0_pin2, + DAILINK_COMP_ARRAY(COMP_CPU("SDW0 Pin2"))); +SND_SOC_DAILINK_DEF(sdw0_pin3, + DAILINK_COMP_ARRAY(COMP_CPU("SDW0 Pin3"))); +SND_SOC_DAILINK_DEF(sdw0_codec, + DAILINK_COMP_ARRAY(COMP_CODEC("sdw:0:25d:711:0", "rt711-aif1"))); + +SND_SOC_DAILINK_DEF(platform, + DAILINK_COMP_ARRAY(COMP_PLATFORM("0000:00:1f.3"))); + +struct snd_soc_dai_link dailink[] = { + { + .name = "SDW0-Playback", + .id = 0, + .init = headset_init, + .no_pcm = 1, + .dpcm_playback = 1, + .nonatomic = true, + SND_SOC_DAILINK_REG(sdw0_pin2, sdw0_codec, platform), + }, + { + .name = "SDW0-Capture", + .id = 1, + .no_pcm = 1, + .dpcm_capture = 1, + .nonatomic = true, + SND_SOC_DAILINK_REG(sdw0_pin3, sdw0_codec, platform), + }, +}; + +/* SoC card */ +static struct snd_soc_card card_sdw_rt711 = { + .name = "sdw-rt711", + .dai_link = dailink, + .num_links = ARRAY_SIZE(dailink), + .controls = controls, + .num_controls = ARRAY_SIZE(controls), + .dapm_widgets = widgets, + .num_dapm_widgets = ARRAY_SIZE(widgets), + .dapm_routes = map, + .num_dapm_routes = ARRAY_SIZE(map), + .late_probe = card_late_probe, +}; + +static int mc_probe(struct platform_device *pdev) +{ + struct mc_private *ctx; + struct snd_soc_acpi_mach *mach; + const char *platform_name; + struct snd_soc_card *card = &card_sdw_rt711; + int ret; + + dev_dbg(&pdev->dev, "Entry %s\n", __func__); + + ctx = devm_kzalloc(&pdev->dev, sizeof(*ctx), GFP_KERNEL); + if (!ctx) + return -ENOMEM; + +#if IS_ENABLED(CONFIG_SND_SOC_HDAC_HDMI) + INIT_LIST_HEAD(&ctx->hdmi_pcm_list); +#endif + + card->dev = &pdev->dev; + + /* override platform name, if required */ + mach = (&pdev->dev)->platform_data; + platform_name = mach->mach_params.platform; + + ret = snd_soc_fixup_dai_links_platform_name(card, platform_name); + if (ret) + return ret; + + ctx->common_hdmi_codec_drv = mach->mach_params.common_hdmi_codec_drv; + + snd_soc_card_set_drvdata(card, ctx); + + /* Register the card */ + ret = devm_snd_soc_register_card(&pdev->dev, card); + if (ret) { + dev_err(card->dev, "snd_soc_register_card failed %d\n", ret); + return ret; + } + + platform_set_drvdata(pdev, card); + + return ret; +} + +static struct platform_driver sdw_rt711_driver = { + .driver = { + .name = "sdw_rt711", + .pm = &snd_soc_pm_ops, + }, + .probe = mc_probe, +}; + +module_platform_driver(sdw_rt711_driver); + +MODULE_DESCRIPTION("ASoC SoundWire RT711 Machine driver"); +MODULE_AUTHOR("Bard Liao "); +MODULE_AUTHOR("Pierre-Louis Bossart "); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:sdw_rt711"); diff --git a/sound/soc/intel/boards/tgl_rt1308.c b/sound/soc/intel/boards/sdw_rt711_i2s_rt1308.c similarity index 54% rename from sound/soc/intel/boards/tgl_rt1308.c rename to sound/soc/intel/boards/sdw_rt711_i2s_rt1308.c index c9e3c479b27cd6..5d8ae497a5e4ac 100644 --- a/sound/soc/intel/boards/tgl_rt1308.c +++ b/sound/soc/intel/boards/sdw_rt711_i2s_rt1308.c @@ -3,45 +3,50 @@ // Copyright(c) 2019 Intel Corporation. All rights reserved. /* - * tgl_rt1308.c - ASoc Machine driver for Intel platforms - * with RT1308 codec. + * tgl_rt711_rt1308.c - ASoc Machine driver for Intel platforms + * with RT711 codec over SoundWire and RT1308 amplifier over I2S */ #include +#include #include +#include +#include +#include +#include +#include +#include #include -#include - -#include +#include #include #include #include #include #include - #include "../../codecs/rt1308.h" #include "../../codecs/hdac_hdmi.h" #include "hda_dsp_common.h" -struct tgl_card_private { +struct mc_private { struct list_head hdmi_pcm_list; bool common_hdmi_codec_drv; + struct snd_soc_jack sdw_headset; }; #if IS_ENABLED(CONFIG_SND_SOC_HDAC_HDMI) -static struct snd_soc_jack tgl_hdmi[4]; +static struct snd_soc_jack hdmi[4]; -struct tgl_hdmi_pcm { +struct hdmi_pcm { struct list_head head; struct snd_soc_dai *codec_dai; int device; }; -static int tgl_hdmi_init(struct snd_soc_pcm_runtime *rtd) +static int hdmi_init(struct snd_soc_pcm_runtime *rtd) { - struct tgl_card_private *ctx = snd_soc_card_get_drvdata(rtd->card); + struct mc_private *ctx = snd_soc_card_get_drvdata(rtd->card); struct snd_soc_dai *dai = rtd->codec_dai; - struct tgl_hdmi_pcm *pcm; + struct hdmi_pcm *pcm; pcm = devm_kzalloc(rtd->card->dev, sizeof(*pcm), GFP_KERNEL); if (!pcm) @@ -57,15 +62,15 @@ static int tgl_hdmi_init(struct snd_soc_pcm_runtime *rtd) } #define NAME_SIZE 32 -static int tgl_card_late_probe(struct snd_soc_card *card) +static int card_late_probe(struct snd_soc_card *card) { - struct tgl_card_private *ctx = snd_soc_card_get_drvdata(card); - struct tgl_hdmi_pcm *pcm; + struct mc_private *ctx = snd_soc_card_get_drvdata(card); + struct hdmi_pcm *pcm; struct snd_soc_component *component = NULL; int err, i = 0; char jack_name[NAME_SIZE]; - pcm = list_first_entry(&ctx->hdmi_pcm_list, struct tgl_hdmi_pcm, + pcm = list_first_entry(&ctx->hdmi_pcm_list, struct hdmi_pcm, head); component = pcm->codec_dai->component; @@ -77,14 +82,14 @@ static int tgl_card_late_probe(struct snd_soc_card *card) snprintf(jack_name, sizeof(jack_name), "HDMI/DP, pcm=%d Jack", pcm->device); err = snd_soc_card_jack_new(card, jack_name, - SND_JACK_AVOUT, &tgl_hdmi[i], + SND_JACK_AVOUT, &hdmi[i], NULL, 0); if (err) return err; err = hdac_hdmi_jack_init(pcm->codec_dai, pcm->device, - &tgl_hdmi[i]); + &hdmi[i]); if (err < 0) return err; @@ -97,14 +102,61 @@ static int tgl_card_late_probe(struct snd_soc_card *card) return hdac_hdmi_jack_port_init(component, &card->dapm); } #else -static int tgl_card_late_probe(struct snd_soc_card *card) +static int card_late_probe(struct snd_soc_card *card) { return 0; } #endif -static int tgl_rt1308_hw_params(struct snd_pcm_substream *substream, - struct snd_pcm_hw_params *params) +static struct snd_soc_jack_pin sdw_jack_pins[] = { + { + .pin = "Headphone", + .mask = SND_JACK_HEADPHONE, + }, + { + .pin = "Headset Mic", + .mask = SND_JACK_MICROPHONE, + }, +}; + +static int headset_init(struct snd_soc_pcm_runtime *rtd) +{ + struct mc_private *ctx = snd_soc_card_get_drvdata(rtd->card); + struct snd_soc_component *component = rtd->codec_dai->component; + struct snd_soc_jack *jack; + int ret; + + ret = snd_soc_card_jack_new(rtd->card, "Headset Jack", + SND_JACK_HEADSET | SND_JACK_BTN_0 | + SND_JACK_BTN_1 | SND_JACK_BTN_2 | + SND_JACK_BTN_3, + &ctx->sdw_headset, + sdw_jack_pins, + ARRAY_SIZE(sdw_jack_pins)); + if (ret) { + dev_err(rtd->card->dev, "Headset Jack creation failed: %d\n", + ret); + return ret; + } + + jack = &ctx->sdw_headset; + + snd_jack_set_key(jack->jack, SND_JACK_BTN_0, KEY_VOLUMEUP); + snd_jack_set_key(jack->jack, SND_JACK_BTN_1, KEY_PLAYPAUSE); + snd_jack_set_key(jack->jack, SND_JACK_BTN_2, KEY_VOLUMEDOWN); + snd_jack_set_key(jack->jack, SND_JACK_BTN_3, KEY_VOICECOMMAND); + + ret = snd_soc_component_set_jack(component, jack, NULL); + + if (ret) + dev_err(rtd->card->dev, "Headset Jack call-back failed: %d\n", + ret); + + return ret; +} + +static int rt1308_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) { struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_card *card = rtd->card; @@ -136,27 +188,39 @@ static int tgl_rt1308_hw_params(struct snd_pcm_substream *substream, } /* machine stream operations */ -static struct snd_soc_ops tgl_rt1308_ops = { - .hw_params = tgl_rt1308_hw_params, +static struct snd_soc_ops rt1308_ops = { + .hw_params = rt1308_hw_params, }; -static const struct snd_soc_dapm_widget tgl_rt1308_dapm_widgets[] = { +static const struct snd_soc_dapm_widget widgets[] = { + SND_SOC_DAPM_HP("Headphone", NULL), + SND_SOC_DAPM_MIC("Headset Mic", NULL), SND_SOC_DAPM_SPK("Speakers", NULL), SND_SOC_DAPM_MIC("SoC DMIC", NULL), }; -static const struct snd_kcontrol_new tgl_rt1308_controls[] = { - SOC_DAPM_PIN_SWITCH("Speakers"), -}; +static const struct snd_soc_dapm_route map[] = { + { "Headphone", NULL, "HP" }, + { "MIC2", NULL, "Headset Mic" }, -static const struct snd_soc_dapm_route tgl_rt1308_dapm_routes[] = { { "Speakers", NULL, "SPOL" }, { "Speakers", NULL, "SPOR" }, + { "DMic", NULL, "SoC DMIC"}, +}; - /* digital mics */ - {"DMic", NULL, "SoC DMIC"}, +static const struct snd_kcontrol_new controls[] = { + SOC_DAPM_PIN_SWITCH("Headphone"), + SOC_DAPM_PIN_SWITCH("Headset Mic"), + SOC_DAPM_PIN_SWITCH("Speakers"), }; +SND_SOC_DAILINK_DEF(sdw0_pin2, + DAILINK_COMP_ARRAY(COMP_CPU("SDW0 Pin2"))); +SND_SOC_DAILINK_DEF(sdw0_pin3, + DAILINK_COMP_ARRAY(COMP_CPU("SDW0 Pin3"))); +SND_SOC_DAILINK_DEF(sdw0_codec, + DAILINK_COMP_ARRAY(COMP_CODEC("sdw:0:25d:711:0", "rt711-aif1"))); + SND_SOC_DAILINK_DEF(ssp2_pin, DAILINK_COMP_ARRAY(COMP_CPU("SSP2 Pin"))); @@ -173,6 +237,7 @@ SND_SOC_DAILINK_DEF(dmic_codec, SND_SOC_DAILINK_DEF(dmic16k, DAILINK_COMP_ARRAY(COMP_CPU("DMIC16k Pin"))); +#if IS_ENABLED(CONFIG_SND_SOC_HDAC_HDMI) SND_SOC_DAILINK_DEF(idisp1_pin, DAILINK_COMP_ARRAY(COMP_CPU("iDisp1 Pin"))); SND_SOC_DAILINK_DEF(idisp1_codec, @@ -192,20 +257,38 @@ SND_SOC_DAILINK_DEF(idisp4_pin, DAILINK_COMP_ARRAY(COMP_CPU("iDisp4 Pin"))); SND_SOC_DAILINK_DEF(idisp4_codec, DAILINK_COMP_ARRAY(COMP_CODEC("ehdaudio0D2", "intel-hdmi-hifi4"))); +#endif -static struct snd_soc_dai_link tgl_rt1308_dailink[] = { +struct snd_soc_dai_link dailink[] = { + { + .name = "SDW0-Playback", + .id = 0, + .init = headset_init, + .no_pcm = 1, + .dpcm_playback = 1, + .nonatomic = true, + SND_SOC_DAILINK_REG(sdw0_pin2, sdw0_codec, platform), + }, + { + .name = "SDW0-Capture", + .id = 1, + .no_pcm = 1, + .dpcm_capture = 1, + .nonatomic = true, + SND_SOC_DAILINK_REG(sdw0_pin3, sdw0_codec, platform), + }, { .name = "SSP2-Codec", - .id = 0, + .id = 2, .no_pcm = 1, - .ops = &tgl_rt1308_ops, + .ops = &rt1308_ops, .dpcm_playback = 1, .nonatomic = true, SND_SOC_DAILINK_REG(ssp2_pin, ssp2_codec, platform), }, { .name = "dmic01", - .id = 1, + .id = 3, .ignore_suspend = 1, .dpcm_capture = 1, .no_pcm = 1, @@ -213,7 +296,7 @@ static struct snd_soc_dai_link tgl_rt1308_dailink[] = { }, { .name = "dmic16k", - .id = 2, + .id = 4, .ignore_suspend = 1, .dpcm_capture = 1, .no_pcm = 1, @@ -222,32 +305,32 @@ static struct snd_soc_dai_link tgl_rt1308_dailink[] = { #if IS_ENABLED(CONFIG_SND_SOC_HDAC_HDMI) { .name = "iDisp1", - .id = 3, - .init = tgl_hdmi_init, + .id = 5, + .init = hdmi_init, .dpcm_playback = 1, .no_pcm = 1, SND_SOC_DAILINK_REG(idisp1_pin, idisp1_codec, platform), }, { .name = "iDisp2", - .id = 4, - .init = tgl_hdmi_init, + .id = 6, + .init = hdmi_init, .dpcm_playback = 1, .no_pcm = 1, SND_SOC_DAILINK_REG(idisp2_pin, idisp2_codec, platform), }, { .name = "iDisp3", - .id = 5, - .init = tgl_hdmi_init, + .id = 7, + .init = hdmi_init, .dpcm_playback = 1, .no_pcm = 1, SND_SOC_DAILINK_REG(idisp3_pin, idisp3_codec, platform), }, { .name = "iDisp4", - .id = 6, - .init = tgl_hdmi_init, + .id = 8, + .init = hdmi_init, .dpcm_playback = 1, .no_pcm = 1, SND_SOC_DAILINK_REG(idisp4_pin, idisp4_codec, platform), @@ -255,41 +338,45 @@ static struct snd_soc_dai_link tgl_rt1308_dailink[] = { #endif }; -/* audio machine driver */ -static struct snd_soc_card tgl_rt1308_card = { - .name = "tgl_rt1308", - .owner = THIS_MODULE, - .dai_link = tgl_rt1308_dailink, - .num_links = ARRAY_SIZE(tgl_rt1308_dailink), - .controls = tgl_rt1308_controls, - .num_controls = ARRAY_SIZE(tgl_rt1308_controls), - .dapm_widgets = tgl_rt1308_dapm_widgets, - .num_dapm_widgets = ARRAY_SIZE(tgl_rt1308_dapm_widgets), - .dapm_routes = tgl_rt1308_dapm_routes, - .num_dapm_routes = ARRAY_SIZE(tgl_rt1308_dapm_routes), - .late_probe = tgl_card_late_probe, +/* SoC card */ +static struct snd_soc_card card_rt711_rt1308 = { + .name = "rt711-rt1308", + .dai_link = dailink, + .num_links = ARRAY_SIZE(dailink), + .controls = controls, + .num_controls = ARRAY_SIZE(controls), + .dapm_widgets = widgets, + .num_dapm_widgets = ARRAY_SIZE(widgets), + .dapm_routes = map, + .num_dapm_routes = ARRAY_SIZE(map), + .late_probe = card_late_probe, }; -static int tgl_rt1308_probe(struct platform_device *pdev) +static int mc_probe(struct platform_device *pdev) { + struct mc_private *ctx; struct snd_soc_acpi_mach *mach; - struct tgl_card_private *ctx; - struct snd_soc_card *card = &tgl_rt1308_card; + const char *platform_name; + struct snd_soc_card *card = &card_rt711_rt1308; int ret; - card->dev = &pdev->dev; + dev_dbg(&pdev->dev, "Entry %s\n", __func__); ctx = devm_kzalloc(&pdev->dev, sizeof(*ctx), GFP_KERNEL); if (!ctx) return -ENOMEM; - if (IS_ENABLED(CONFIG_SND_SOC_HDAC_HDMI)) - INIT_LIST_HEAD(&ctx->hdmi_pcm_list); +#if IS_ENABLED(CONFIG_SND_SOC_HDAC_HDMI) + INIT_LIST_HEAD(&ctx->hdmi_pcm_list); +#endif + + card->dev = &pdev->dev; + /* override platform name, if required */ mach = (&pdev->dev)->platform_data; + platform_name = mach->mach_params.platform; - ret = snd_soc_fixup_dai_links_platform_name(card, - mach->mach_params.platform); + ret = snd_soc_fixup_dai_links_platform_name(card, platform_name); if (ret) return ret; @@ -300,17 +387,17 @@ static int tgl_rt1308_probe(struct platform_device *pdev) return devm_snd_soc_register_card(&pdev->dev, card); } -static struct platform_driver tgl_rt1308_driver = { +static struct platform_driver rt711_rt1308_driver = { .driver = { - .name = "tgl_rt1308", + .name = "rt711_rt1308", .pm = &snd_soc_pm_ops, }, - .probe = tgl_rt1308_probe, + .probe = mc_probe, }; -module_platform_driver(tgl_rt1308_driver); +module_platform_driver(rt711_rt1308_driver); MODULE_AUTHOR("Xiuli Pan"); -MODULE_DESCRIPTION("ASoC Intel(R) Tiger Lake + RT1308 Machine driver"); +MODULE_DESCRIPTION("ASoC SoundWire RT711 + RT1308 Machine driver"); MODULE_LICENSE("GPL v2"); -MODULE_ALIAS("platform:tgl_rt1308"); +MODULE_ALIAS("platform:rt711_rt1308"); diff --git a/sound/soc/intel/boards/sdw_rt711_rt1308_rt715.c b/sound/soc/intel/boards/sdw_rt711_rt1308_rt715.c new file mode 100644 index 00000000000000..6c69a7ea57ec61 --- /dev/null +++ b/sound/soc/intel/boards/sdw_rt711_rt1308_rt715.c @@ -0,0 +1,479 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (c) 2019 Intel Corporation + +/* + * sdw_rt711_rt1308_rt715 - ASOC Machine driver for Intel SoundWire platforms + * connected to 3 Realtek devices + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "../../codecs/hdac_hdmi.h" +#include "hda_dsp_common.h" + +/* comment out this define for mono configurations */ + +#define MAX_NO_PROPS 2 + +extern struct bus_type sdw_bus_type; + +enum { + SOF_RT711_JD_SRC_JD1 = 1, + SOF_RT711_JD_SRC_JD2 = 2, +}; + +#define SOF_RT711_JDSRC(quirk) ((quirk) & GENMASK(1, 0)) +#define SOF_SDW_MONO_SPK BIT(2) + +static unsigned long sof_rt711_rt1308_rt715_quirk = SOF_RT711_JD_SRC_JD1; + +struct mc_private { + struct list_head hdmi_pcm_list; + bool common_hdmi_codec_drv; + struct snd_soc_jack sdw_headset; +}; + +#if IS_ENABLED(CONFIG_SND_SOC_HDAC_HDMI) +static struct snd_soc_jack hdmi[3]; + +struct hdmi_pcm { + struct list_head head; + struct snd_soc_dai *codec_dai; + int device; +}; + +static int hdmi_init(struct snd_soc_pcm_runtime *rtd) +{ + struct mc_private *ctx = snd_soc_card_get_drvdata(rtd->card); + struct snd_soc_dai *dai = rtd->codec_dai; + struct hdmi_pcm *pcm; + + pcm = devm_kzalloc(rtd->card->dev, sizeof(*pcm), GFP_KERNEL); + if (!pcm) + return -ENOMEM; + + /* dai_link id is 1:1 mapped to the PCM device */ + pcm->device = rtd->dai_link->id; + pcm->codec_dai = dai; + + list_add_tail(&pcm->head, &ctx->hdmi_pcm_list); + + return 0; +} + +#define NAME_SIZE 32 +static int card_late_probe(struct snd_soc_card *card) +{ + struct mc_private *ctx = snd_soc_card_get_drvdata(card); + struct hdmi_pcm *pcm; + struct snd_soc_component *component = NULL; + int err, i = 0; + char jack_name[NAME_SIZE]; + + pcm = list_first_entry(&ctx->hdmi_pcm_list, struct hdmi_pcm, + head); + component = pcm->codec_dai->component; + + if (ctx->common_hdmi_codec_drv) + return hda_dsp_hdmi_build_controls(card, component); + + list_for_each_entry(pcm, &ctx->hdmi_pcm_list, head) { + component = pcm->codec_dai->component; + snprintf(jack_name, sizeof(jack_name), + "HDMI/DP, pcm=%d Jack", pcm->device); + err = snd_soc_card_jack_new(card, jack_name, + SND_JACK_AVOUT, &hdmi[i], + NULL, 0); + + if (err) + return err; + + err = snd_jack_add_new_kctl(hdmi[i].jack, + jack_name, SND_JACK_AVOUT); + if (err) + dev_warn(component->dev, "failed creating Jack kctl\n"); + + err = hdac_hdmi_jack_init(pcm->codec_dai, pcm->device, + &hdmi[i]); + if (err < 0) + return err; + + i++; + } + + if (!component) + return -EINVAL; + + return hdac_hdmi_jack_port_init(component, &card->dapm); +} +#else +static int card_late_probe(struct snd_soc_card *card) +{ + return 0; +} +#endif + +static struct snd_soc_jack_pin sdw_jack_pins[] = { + { + .pin = "Headphone", + .mask = SND_JACK_HEADPHONE, + }, + { + .pin = "Headset Mic", + .mask = SND_JACK_MICROPHONE, + }, +}; + +static int headset_init(struct snd_soc_pcm_runtime *rtd) +{ + struct mc_private *ctx = snd_soc_card_get_drvdata(rtd->card); + struct snd_soc_component *component = rtd->codec_dai->component; + struct snd_soc_jack *jack; + int ret; + + ret = snd_soc_card_jack_new(rtd->card, "Headset Jack", + SND_JACK_HEADSET | SND_JACK_BTN_0 | + SND_JACK_BTN_1 | SND_JACK_BTN_2 | + SND_JACK_BTN_3, + &ctx->sdw_headset, + sdw_jack_pins, + ARRAY_SIZE(sdw_jack_pins)); + if (ret) { + dev_err(rtd->card->dev, "Headset Jack creation failed: %d\n", + ret); + return ret; + } + + jack = &ctx->sdw_headset; + + snd_jack_set_key(jack->jack, SND_JACK_BTN_0, KEY_VOLUMEUP); + snd_jack_set_key(jack->jack, SND_JACK_BTN_1, KEY_PLAYPAUSE); + snd_jack_set_key(jack->jack, SND_JACK_BTN_2, KEY_VOLUMEDOWN); + snd_jack_set_key(jack->jack, SND_JACK_BTN_3, KEY_VOICECOMMAND); + + ret = snd_soc_component_set_jack(component, jack, NULL); + + if (ret) + dev_err(rtd->card->dev, "Headset Jack call-back failed: %d\n", + ret); + + return ret; +} + +static int sof_rt711_rt1308_rt715_quirk_cb(const struct dmi_system_id *id) +{ + sof_rt711_rt1308_rt715_quirk = (unsigned long)id->driver_data; + return 1; +} + +static const struct dmi_system_id sof_sdw_rt711_rt1308_rt715_quirk_table[] = { + { + .callback = sof_rt711_rt1308_rt715_quirk_cb, + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc"), + }, + .driver_data = (void *)(SOF_RT711_JD_SRC_JD2 | + SOF_SDW_MONO_SPK), + }, + {} +}; + +/* + * Note this MUST be called before snd_soc_register_card(), so that the props + * are in place before the codec component driver's probe function parses them. + */ +static int sof_rt711_add_codec_device_props(const char *sdw_dev_name) +{ + struct property_entry props[MAX_NO_PROPS] = {}; + struct device *sdw_dev; + int ret, cnt = 0; + + sdw_dev = bus_find_device_by_name(&sdw_bus_type, NULL, sdw_dev_name); + if (!sdw_dev) + return -EPROBE_DEFER; + + if (SOF_RT711_JDSRC(sof_rt711_rt1308_rt715_quirk)) { + props[cnt++] = PROPERTY_ENTRY_U32( + "realtek,jd-src", + SOF_RT711_JDSRC(sof_rt711_rt1308_rt715_quirk)); + } + + ret = device_add_properties(sdw_dev, props); + put_device(sdw_dev); + + return ret; +} + +static const struct snd_soc_dapm_widget widgets[] = { + SND_SOC_DAPM_HP("Headphone", NULL), + SND_SOC_DAPM_MIC("Headset Mic", NULL), + SND_SOC_DAPM_SPK("Speaker", NULL), +}; + +static const struct snd_soc_dapm_route map[] = { + /* Headphones */ + { "Headphone", NULL, "rt711 HP" }, + { "rt711 MIC2", NULL, "Headset Mic" }, + /* Speakers */ + { "Speaker", NULL, "rt1308-1 SPOL" }, + { "Speaker", NULL, "rt1308-1 SPOR" }, +}; + +static const struct snd_soc_dapm_route second_speaker_map[] = { + { "Speaker", NULL, "rt1308-2 SPOL" }, + { "Speaker", NULL, "rt1308-2 SPOR" }, +}; + +static const struct snd_kcontrol_new controls[] = { + SOC_DAPM_PIN_SWITCH("Headphone"), + SOC_DAPM_PIN_SWITCH("Headset Mic"), + SOC_DAPM_PIN_SWITCH("Speaker"), +}; + +static int second_spk_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_card *card = rtd->card; + int ret; + + ret = snd_soc_dapm_add_routes(&card->dapm, second_speaker_map, + ARRAY_SIZE(second_speaker_map)); + + if (ret) + dev_err(rtd->dev, "second Speaker map addition failed: %d\n", + ret); + return ret; +} + +SND_SOC_DAILINK_DEF(sdw0_pin2, + DAILINK_COMP_ARRAY(COMP_CPU("SDW0 Pin2"))); +SND_SOC_DAILINK_DEF(sdw0_pin3, + DAILINK_COMP_ARRAY(COMP_CPU("SDW0 Pin3"))); +SND_SOC_DAILINK_DEF(sdw0_codec, + DAILINK_COMP_ARRAY(COMP_CODEC("sdw:0:25d:711:0", "rt711-aif1"))); + +SND_SOC_DAILINK_DEF(sdw1_pin2, + DAILINK_COMP_ARRAY(COMP_CPU("SDW1 Pin2"))); +SND_SOC_DAILINK_DEF(sdw1_codec, + DAILINK_COMP_ARRAY(COMP_CODEC("sdw:1:25d:1308:0", "rt1308-aif"))); + +SND_SOC_DAILINK_DEF(sdw2_pin2, + DAILINK_COMP_ARRAY(COMP_CPU("SDW2 Pin2"))); +SND_SOC_DAILINK_DEF(sdw2_codec, + DAILINK_COMP_ARRAY(COMP_CODEC("sdw:2:25d:1308:0", "rt1308-aif"))); + +SND_SOC_DAILINK_DEF(sdw3_pin2, + DAILINK_COMP_ARRAY(COMP_CPU("SDW3 Pin2"))); +SND_SOC_DAILINK_DEF(sdw3_codec, + DAILINK_COMP_ARRAY(COMP_CODEC("sdw:3:25d:715:0", "rt715-aif2"))); + +#if IS_ENABLED(CONFIG_SND_SOC_HDAC_HDMI) +SND_SOC_DAILINK_DEF(idisp1_pin, + DAILINK_COMP_ARRAY(COMP_CPU("iDisp1 Pin"))); +SND_SOC_DAILINK_DEF(idisp1_codec, + DAILINK_COMP_ARRAY(COMP_CODEC("ehdaudio0D2", "intel-hdmi-hifi1"))); + +SND_SOC_DAILINK_DEF(idisp2_pin, + DAILINK_COMP_ARRAY(COMP_CPU("iDisp2 Pin"))); +SND_SOC_DAILINK_DEF(idisp2_codec, + DAILINK_COMP_ARRAY(COMP_CODEC("ehdaudio0D2", "intel-hdmi-hifi2"))); + +SND_SOC_DAILINK_DEF(idisp3_pin, + DAILINK_COMP_ARRAY(COMP_CPU("iDisp3 Pin"))); +SND_SOC_DAILINK_DEF(idisp3_codec, + DAILINK_COMP_ARRAY(COMP_CODEC("ehdaudio0D2", "intel-hdmi-hifi3"))); +#endif + +SND_SOC_DAILINK_DEF(platform, + DAILINK_COMP_ARRAY(COMP_PLATFORM("0000:00:1f.3"))); + +static struct snd_soc_codec_conf codec_conf[] = { + { + .dev_name = "sdw:0:25d:711:0", + .name_prefix = "rt711", + }, + { + .dev_name = "sdw:1:25d:1308:0", + .name_prefix = "rt1308-1", + }, + { + .dev_name = "sdw:3:25d:715:0", + .name_prefix = "rt715", + }, + { + .dev_name = "sdw:2:25d:1308:0", + .name_prefix = "rt1308-2", + }, + +}; + +struct snd_soc_dai_link dailink[] = { + { + .name = "SDW0-Playback", + .id = 0, + .init = headset_init, + .no_pcm = 1, + .dpcm_playback = 1, + .nonatomic = true, + SND_SOC_DAILINK_REG(sdw0_pin2, sdw0_codec, platform), + }, + { + .name = "SDW0-Capture", + .id = 1, + .no_pcm = 1, + .dpcm_capture = 1, + .nonatomic = true, + SND_SOC_DAILINK_REG(sdw0_pin3, sdw0_codec, platform), + }, + { + .name = "SDW1-Playback", + .id = 2, + .no_pcm = 1, + .dpcm_playback = 1, + .nonatomic = true, + SND_SOC_DAILINK_REG(sdw1_pin2, sdw1_codec, platform), + }, + { + .name = "SDW3-Capture", + .id = 4, + .no_pcm = 1, + .dpcm_capture = 1, + .nonatomic = true, + SND_SOC_DAILINK_REG(sdw3_pin2, sdw3_codec, platform), + }, + +#if IS_ENABLED(CONFIG_SND_SOC_HDAC_HDMI) + { + .name = "iDisp1", + .id = 5, + .init = hdmi_init, + .no_pcm = 1, + .dpcm_playback = 1, + SND_SOC_DAILINK_REG(idisp1_pin, idisp1_codec, platform), + }, + { + .name = "iDisp2", + .id = 6, + .init = hdmi_init, + .no_pcm = 1, + .dpcm_playback = 1, + SND_SOC_DAILINK_REG(idisp2_pin, idisp2_codec, platform), + }, + { + .name = "iDisp3", + .id = 7, + .init = hdmi_init, + .no_pcm = 1, + .dpcm_playback = 1, + SND_SOC_DAILINK_REG(idisp3_pin, idisp3_codec, platform), + }, +#endif + { + .name = "SDW2-Playback", + .id = 3, + .init = second_spk_init, + .no_pcm = 1, + .dpcm_playback = 1, + .nonatomic = true, + SND_SOC_DAILINK_REG(sdw2_pin2, sdw2_codec, platform), + }, +}; + +/* SoC card */ +static struct snd_soc_card card_rt700_rt1308_rt715 = { + .name = "sdw-rt711-1308-715", + .dai_link = dailink, + .num_links = ARRAY_SIZE(dailink), + .controls = controls, + .num_controls = ARRAY_SIZE(controls), + .dapm_widgets = widgets, + .num_dapm_widgets = ARRAY_SIZE(widgets), + .dapm_routes = map, + .num_dapm_routes = ARRAY_SIZE(map), + .late_probe = card_late_probe, + .codec_conf = codec_conf, + .num_configs = ARRAY_SIZE(codec_conf), +}; + +static int mc_probe(struct platform_device *pdev) +{ + struct mc_private *ctx; + struct snd_soc_acpi_mach *mach; + const char *platform_name; + struct snd_soc_card *card = &card_rt700_rt1308_rt715; + int ret; + + dev_dbg(&pdev->dev, "Entry %s\n", __func__); + + ctx = devm_kzalloc(&pdev->dev, sizeof(*ctx), GFP_KERNEL); + if (!ctx) + return -ENOMEM; + + dmi_check_system(sof_sdw_rt711_rt1308_rt715_quirk_table); + +#if IS_ENABLED(CONFIG_SND_SOC_HDAC_HDMI) + INIT_LIST_HEAD(&ctx->hdmi_pcm_list); +#endif + + card->dev = &pdev->dev; + + /* override platform name, if required */ + mach = (&pdev->dev)->platform_data; + platform_name = mach->mach_params.platform; + + ret = snd_soc_fixup_dai_links_platform_name(card, platform_name); + if (ret) + return ret; + + ctx->common_hdmi_codec_drv = mach->mach_params.common_hdmi_codec_drv; + + snd_soc_card_set_drvdata(card, ctx); + + sof_rt711_add_codec_device_props("sdw:0:25d:711:0"); + + if (sof_rt711_rt1308_rt715_quirk & SOF_SDW_MONO_SPK) { + /* Remove rt1308-2 codec from dailink and codec_conf */ + card->num_links = ARRAY_SIZE(dailink) - 1 ; + card->num_configs = ARRAY_SIZE(codec_conf) - 1; + } + + /* Register the card */ + ret = devm_snd_soc_register_card(&pdev->dev, card); + if (ret) { + dev_err(card->dev, "snd_soc_register_card failed %d\n", ret); + return ret; + } + + platform_set_drvdata(pdev, card); + + return ret; +} + +static struct platform_driver sdw_rt711_rt1308_rt715_driver = { + .driver = { + .name = "sdw_rt711_rt1308_rt715", + .pm = &snd_soc_pm_ops, + }, + .probe = mc_probe, +}; + +module_platform_driver(sdw_rt711_rt1308_rt715_driver); + +MODULE_DESCRIPTION("ASoC SoundWire RT711/1308/715 Machine driver"); +MODULE_AUTHOR("Bard Liao "); +MODULE_AUTHOR("Pierre-Louis Bossart "); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:sdw_rt711_rt1308_rt715"); diff --git a/sound/soc/intel/common/soc-acpi-intel-cfl-match.c b/sound/soc/intel/common/soc-acpi-intel-cfl-match.c index d6fd2026d0b863..ff9d6938b9f668 100644 --- a/sound/soc/intel/common/soc-acpi-intel-cfl-match.c +++ b/sound/soc/intel/common/soc-acpi-intel-cfl-match.c @@ -14,5 +14,10 @@ struct snd_soc_acpi_mach snd_soc_acpi_intel_cfl_machines[] = { }; EXPORT_SYMBOL_GPL(snd_soc_acpi_intel_cfl_machines); +struct snd_soc_acpi_mach snd_soc_acpi_intel_cfl_sdw_machines[] = { + {} +}; +EXPORT_SYMBOL_GPL(snd_soc_acpi_intel_cfl_sdw_machines); + MODULE_LICENSE("GPL v2"); MODULE_DESCRIPTION("Intel Common ACPI Match module"); diff --git a/sound/soc/intel/common/soc-acpi-intel-cml-match.c b/sound/soc/intel/common/soc-acpi-intel-cml-match.c index 5d08ae0667380b..58dbd6dac460d8 100644 --- a/sound/soc/intel/common/soc-acpi-intel-cml-match.c +++ b/sound/soc/intel/common/soc-acpi-intel-cml-match.c @@ -52,5 +52,28 @@ struct snd_soc_acpi_mach snd_soc_acpi_intel_cml_machines[] = { }; EXPORT_SYMBOL_GPL(snd_soc_acpi_intel_cml_machines); +struct snd_soc_acpi_mach snd_soc_acpi_intel_cml_sdw_machines[] = { + { + .link_mask = 0xF, /* 4 active links required */ + .drv_name = "sdw_rt711_rt1308_rt715", + .sof_fw_filename = "sof-cml.ri", + .sof_tplg_filename = "sof-cml-rt711-rt1308-rt715.tplg", + }, + { + .link_mask = 0xB, /* 3 active links required */ + .drv_name = "sdw_rt711_rt1308_rt715", + .sof_fw_filename = "sof-cml.ri", + .sof_tplg_filename = "sof-cml-rt711-rt1308-mono-rt715.tplg", + }, + { + .link_mask = 0x2, /* RT700 connected on Link1 */ + .drv_name = "sdw_rt700", + .sof_fw_filename = "sof-cml.ri", + .sof_tplg_filename = "sof-cml-rt700.tplg", + }, + {} +}; +EXPORT_SYMBOL_GPL(snd_soc_acpi_intel_cml_sdw_machines); + MODULE_LICENSE("GPL v2"); MODULE_DESCRIPTION("Intel Common ACPI Match module"); diff --git a/sound/soc/intel/common/soc-acpi-intel-cnl-match.c b/sound/soc/intel/common/soc-acpi-intel-cnl-match.c index 27588841c8b005..828980d5630d46 100644 --- a/sound/soc/intel/common/soc-acpi-intel-cnl-match.c +++ b/sound/soc/intel/common/soc-acpi-intel-cnl-match.c @@ -27,5 +27,10 @@ struct snd_soc_acpi_mach snd_soc_acpi_intel_cnl_machines[] = { }; EXPORT_SYMBOL_GPL(snd_soc_acpi_intel_cnl_machines); +struct snd_soc_acpi_mach snd_soc_acpi_intel_cnl_sdw_machines[] = { + {}, +}; +EXPORT_SYMBOL_GPL(snd_soc_acpi_intel_cnl_sdw_machines); + MODULE_LICENSE("GPL v2"); MODULE_DESCRIPTION("Intel Common ACPI Match module"); diff --git a/sound/soc/intel/common/soc-acpi-intel-icl-match.c b/sound/soc/intel/common/soc-acpi-intel-icl-match.c index 38977669b57653..63b6197a605606 100644 --- a/sound/soc/intel/common/soc-acpi-intel-icl-match.c +++ b/sound/soc/intel/common/soc-acpi-intel-icl-match.c @@ -33,5 +33,22 @@ struct snd_soc_acpi_mach snd_soc_acpi_intel_icl_machines[] = { }; EXPORT_SYMBOL_GPL(snd_soc_acpi_intel_icl_machines); +struct snd_soc_acpi_mach snd_soc_acpi_intel_icl_sdw_machines[] = { + { + .link_mask = 0xF, /* 4 active links required */ + .drv_name = "sdw_rt711_rt1308_rt715", + .sof_fw_filename = "sof-icl.ri", + .sof_tplg_filename = "sof-icl-rt711-rt1308-rt715.tplg", + }, + { + .link_mask = 0x1, /* rt700 connected on link0 */ + .drv_name = "sdw_rt700", + .sof_fw_filename = "sof-icl.ri", + .sof_tplg_filename = "sof-icl-rt700.tplg", + }, + {}, +}; +EXPORT_SYMBOL_GPL(snd_soc_acpi_intel_icl_sdw_machines); + MODULE_LICENSE("GPL v2"); MODULE_DESCRIPTION("Intel Common ACPI Match module"); diff --git a/sound/soc/intel/common/soc-acpi-intel-tgl-match.c b/sound/soc/intel/common/soc-acpi-intel-tgl-match.c index 57a6298d6dca31..e74f428192ccc7 100644 --- a/sound/soc/intel/common/soc-acpi-intel-tgl-match.c +++ b/sound/soc/intel/common/soc-acpi-intel-tgl-match.c @@ -12,13 +12,25 @@ struct snd_soc_acpi_mach snd_soc_acpi_intel_tgl_machines[] = { { .id = "10EC1308", - .drv_name = "tgl_rt1308", + .drv_name = "rt711_rt1308", + .link_mask = 0x1, /* RT711 on SoundWire link0 */ .sof_fw_filename = "sof-tgl.ri", - .sof_tplg_filename = "sof-tgl-rt1308.tplg", + .sof_tplg_filename = "sof-tgl-rt711-rt1308.tplg", }, {}, }; EXPORT_SYMBOL_GPL(snd_soc_acpi_intel_tgl_machines); +struct snd_soc_acpi_mach snd_soc_acpi_intel_tgl_sdw_machines[] = { + { + .link_mask = 0x1, /* this will only enable rt711 for now */ + .drv_name = "sdw_rt711_rt1308_rt715", + .sof_fw_filename = "sof-tgl.ri", + .sof_tplg_filename = "sof-tgl-rt711-rt1308-rt715.tplg", + }, + {}, +}; +EXPORT_SYMBOL_GPL(snd_soc_acpi_intel_tgl_sdw_machines); + MODULE_LICENSE("GPL v2"); MODULE_DESCRIPTION("Intel Common ACPI Match module"); diff --git a/sound/soc/sof/intel/Kconfig b/sound/soc/sof/intel/Kconfig index b27fd3fdf335d7..1e29c2cac549eb 100644 --- a/sound/soc/sof/intel/Kconfig +++ b/sound/soc/sof/intel/Kconfig @@ -167,6 +167,7 @@ config SND_SOC_SOF_CANNONLAKE_SUPPORT config SND_SOC_SOF_CANNONLAKE tristate select SND_SOC_SOF_HDA_COMMON + select SND_SOC_SOF_INTEL_SOUNDWIRE_LINK_BASELINE help This option is not user-selectable but automagically handled by 'select' statements at a higher level @@ -182,6 +183,7 @@ config SND_SOC_SOF_COFFEELAKE_SUPPORT config SND_SOC_SOF_COFFEELAKE tristate select SND_SOC_SOF_HDA_COMMON + select SND_SOC_SOF_INTEL_SOUNDWIRE_LINK_BASELINE help This option is not user-selectable but automagically handled by 'select' statements at a higher level @@ -197,6 +199,7 @@ config SND_SOC_SOF_ICELAKE_SUPPORT config SND_SOC_SOF_ICELAKE tristate select SND_SOC_SOF_HDA_COMMON + select SND_SOC_SOF_INTEL_SOUNDWIRE_LINK_BASELINE help This option is not user-selectable but automagically handled by 'select' statements at a higher level @@ -204,6 +207,7 @@ config SND_SOC_SOF_ICELAKE config SND_SOC_SOF_COMETLAKE_LP tristate select SND_SOC_SOF_HDA_COMMON + select SND_SOC_SOF_INTEL_SOUNDWIRE_LINK_BASELINE help This option is not user-selectable but automagically handled by 'select' statements at a higher level @@ -219,6 +223,7 @@ config SND_SOC_SOF_COMETLAKE_LP_SUPPORT config SND_SOC_SOF_COMETLAKE_H tristate select SND_SOC_SOF_HDA_COMMON + select SND_SOC_SOF_INTEL_SOUNDWIRE_LINK_BASELINE help This option is not user-selectable but automagically handled by 'select' statements at a higher level @@ -242,6 +247,7 @@ config SND_SOC_SOF_TIGERLAKE_SUPPORT config SND_SOC_SOF_TIGERLAKE tristate select SND_SOC_SOF_HDA_COMMON + select SND_SOC_SOF_INTEL_SOUNDWIRE_LINK_BASELINE help This option is not user-selectable but automagically handled by 'select' statements at a higher level @@ -257,6 +263,7 @@ config SND_SOC_SOF_ELKHARTLAKE_SUPPORT config SND_SOC_SOF_ELKHARTLAKE tristate select SND_SOC_SOF_HDA_COMMON + select SND_SOC_SOF_INTEL_SOUNDWIRE_LINK_BASELINE help This option is not user-selectable but automagically handled by 'select' statements at a higher level @@ -343,6 +350,29 @@ config SND_SOC_SOF_HDA This option is not user-selectable but automagically handled by 'select' statements at a higher level +config SND_SOC_SOF_INTEL_SOUNDWIRE_LINK + bool "SOF support for SoundWire" + depends on SOUNDWIRE && ACPI + help + This adds support for SoundWire with Sound Open Firmware + for Intel(R) platforms. + Say Y if you want to enable SoundWire links with SOF. + If unsure select "N". + +config SND_SOC_SOF_INTEL_SOUNDWIRE_LINK_BASELINE + tristate + select SND_SOC_SOF_INTEL_SOUNDWIRE if SND_SOC_SOF_INTEL_SOUNDWIRE_LINK + help + This option is not user-selectable but automagically handled by + 'select' statements at a higher level + +config SND_SOC_SOF_INTEL_SOUNDWIRE + tristate + select SOUNDWIRE_INTEL + help + This option is not user-selectable but automagically handled by + 'select' statements at a higher level + endif ## SND_SOC_SOF_INTEL_PCI endif ## SND_SOC_SOF_INTEL_TOPLEVEL diff --git a/sound/soc/sof/intel/hda-ctrl.c b/sound/soc/sof/intel/hda-ctrl.c index df1909e1d95060..72d7fa3a0be4f5 100644 --- a/sound/soc/sof/intel/hda-ctrl.c +++ b/sound/soc/sof/intel/hda-ctrl.c @@ -57,15 +57,32 @@ int hda_dsp_ctrl_get_caps(struct snd_sof_dev *sdev) struct hdac_bus *bus = sof_to_bus(sdev); u32 cap, offset, feature; int count = 0; + int ret; + + /* + * On some devices, one reset cycle is necessary before reading + * capabilities + */ + ret = hda_dsp_ctrl_link_reset(sdev, true); + if (ret < 0) + return ret; + ret = hda_dsp_ctrl_link_reset(sdev, false); + if (ret < 0) + return ret; offset = snd_sof_dsp_read(sdev, HDA_DSP_HDA_BAR, SOF_HDA_LLCH); do { - cap = snd_sof_dsp_read(sdev, HDA_DSP_HDA_BAR, offset); - dev_dbg(sdev->dev, "checking for capabilities at offset 0x%x\n", offset & SOF_HDA_CAP_NEXT_MASK); + cap = snd_sof_dsp_read(sdev, HDA_DSP_HDA_BAR, offset); + + if (cap == -1) { + dev_dbg(bus->dev, "Invalid capability reg read\n"); + break; + } + feature = (cap & SOF_HDA_CAP_ID_MASK) >> SOF_HDA_CAP_ID_OFF; switch (feature) { @@ -98,8 +115,8 @@ int hda_dsp_ctrl_get_caps(struct snd_sof_dev *sdev) bus->mlcap = bus->remap_addr + offset; break; default: - dev_vdbg(sdev->dev, "found capability %d at 0x%x\n", - feature, offset); + dev_dbg(sdev->dev, "found capability %d at 0x%x\n", + feature, offset); break; } diff --git a/sound/soc/sof/intel/hda-dsp.c b/sound/soc/sof/intel/hda-dsp.c index 4a4d318f97ffa5..58388bccf27f30 100644 --- a/sound/soc/sof/intel/hda-dsp.c +++ b/sound/soc/sof/intel/hda-dsp.c @@ -390,6 +390,8 @@ static int hda_suspend(struct snd_sof_dev *sdev, bool runtime_suspend) #endif int ret; + hda_sdw_int_enable(sdev, false); + /* disable IPC interrupts */ hda_dsp_ipc_int_disable(sdev); diff --git a/sound/soc/sof/intel/hda-loader.c b/sound/soc/sof/intel/hda-loader.c index b1783360fe106b..2f5cd2c1ea3c4d 100644 --- a/sound/soc/sof/intel/hda-loader.c +++ b/sound/soc/sof/intel/hda-loader.c @@ -397,6 +397,19 @@ int hda_dsp_pre_fw_run(struct snd_sof_dev *sdev) /* post fw run operations */ int hda_dsp_post_fw_run(struct snd_sof_dev *sdev) { + int ret; + + if (sdev->first_boot) { + ret = hda_sdw_startup(sdev); + if (ret < 0) { + dev_err(sdev->dev, + "error: could not startup SoundWire links\n"); + return ret; + } + } + + hda_sdw_int_enable(sdev, true); + /* re-enable clock gating and power gating */ return hda_dsp_ctrl_clock_power_gating(sdev, true); } diff --git a/sound/soc/sof/intel/hda.c b/sound/soc/sof/intel/hda.c index 91bd88fddac7aa..8e6275097b028c 100644 --- a/sound/soc/sof/intel/hda.c +++ b/sound/soc/sof/intel/hda.c @@ -18,7 +18,9 @@ #include #include +#include #include +#include #include #include #include @@ -34,6 +36,171 @@ #define EXCEPT_MAX_HDR_SIZE 0x400 +#if IS_ENABLED(CONFIG_SND_SOC_SOF_INTEL_SOUNDWIRE) + +static int sdw_params_stream(struct device *dev, + struct sdw_intel_stream_params_data *params_data) +{ + struct snd_sof_dev *sdev = dev_get_drvdata(dev); + struct snd_soc_dai *d = params_data->dai; + struct sof_ipc_dai_config config; + struct sof_ipc_reply reply; + int link_id = params_data->link_id; + int alh_stream_id = params_data->alh_stream_id; + int ret; + u32 size = sizeof(config); + + memset(&config, 0, size); + config.hdr.size = size; + config.hdr.cmd = SOF_IPC_GLB_DAI_MSG | SOF_IPC_DAI_CONFIG; + config.type = SOF_DAI_INTEL_ALH; + config.dai_index = (link_id << 8) | (d->id); + config.alh.stream_id = alh_stream_id; + + /* send message to DSP */ + ret = sof_ipc_tx_message(sdev->ipc, + config.hdr.cmd, &config, size, &reply, + sizeof(reply)); + if (ret < 0) { + dev_err(sdev->dev, + "error: failed to set DAI hw_params for link %d dai->id %d ALH %d\n", + link_id, d->id, alh_stream_id); + } + + return ret; +} + +static int sdw_free_stream(struct device *dev, + struct sdw_intel_stream_free_data *free_data) +{ + struct snd_sof_dev *sdev = dev_get_drvdata(dev); + struct snd_soc_dai *d = free_data->dai; + struct sof_ipc_dai_config config; + struct sof_ipc_reply reply; + int link_id = free_data->link_id; + int ret; + u32 size = sizeof(config); + + memset(&config, 0, size); + config.hdr.size = size; + config.hdr.cmd = SOF_IPC_GLB_DAI_MSG | SOF_IPC_DAI_CONFIG; + config.type = SOF_DAI_INTEL_ALH; + config.dai_index = (link_id << 8) | d->id; + config.alh.stream_id = 0xFFFF; /* invalid value on purpose */ + + /* send message to DSP */ + ret = sof_ipc_tx_message(sdev->ipc, + config.hdr.cmd, &config, size, &reply, + sizeof(reply)); + if (ret < 0) { + dev_err(sdev->dev, + "error: failed to free stream for link %d dai->id %d\n", + link_id, d->id); + } + + return ret; +} + +static const struct sdw_intel_ops sdw_callback = { + .params_stream = sdw_params_stream, + .free_stream = sdw_free_stream, +}; + +void hda_sdw_int_enable(struct snd_sof_dev *sdev, bool enable) +{ + sdw_intel_enable_irq(sdev->bar[HDA_DSP_BAR], enable); +} + +static int hda_sdw_acpi_scan(struct snd_sof_dev *sdev) +{ + struct sof_intel_hda_dev *hdev; + acpi_handle handle; + int ret; + + handle = ACPI_HANDLE(sdev->dev); + + /* save ACPI info for the probe step */ + hdev = sdev->pdata->hw_pdata; + + ret = sdw_intel_acpi_scan(handle, &hdev->info); + if (ret < 0) { + dev_err(sdev->dev, "%s failed\n", __func__); + return -EINVAL; + } + + return 0; +} + +static int hda_sdw_probe(struct snd_sof_dev *sdev) +{ + struct sof_intel_hda_dev *hdev; + struct sdw_intel_res res; + acpi_handle handle; + void *sdw; + + handle = ACPI_HANDLE(sdev->dev); + + hdev = sdev->pdata->hw_pdata; + + memset(&res, 0, sizeof(res)); + + res.mmio_base = sdev->bar[HDA_DSP_BAR]; + res.irq = sdev->ipc_irq; + res.handle = hdev->info.handle; + res.parent = sdev->dev; + res.ops = &sdw_callback; + res.dev = sdev->dev; + + /* + * ops and arg fields are not populated for now, + * they will be needed when the DAI callbacks are + * provided + */ + + /* we could filter links here if needed, e.g for quirks */ + res.count = hdev->info.count; + res.link_mask = hdev->info.link_mask; + + sdw = sdw_intel_probe(&res); + if (!sdw) { + dev_err(sdev->dev, "error: SoundWire probe failed\n"); + return -EINVAL; + } + + /* save context */ + hdev->sdw = sdw; + + return 0; +} + +int hda_sdw_startup(struct snd_sof_dev *sdev) +{ + struct sof_intel_hda_dev *hdev; + + hdev = sdev->pdata->hw_pdata; + + if (!hdev->sdw) + return 0; + + return sdw_intel_startup(hdev->sdw); +} + +static int hda_sdw_exit(struct snd_sof_dev *sdev) +{ + struct sof_intel_hda_dev *hdev; + + hdev = sdev->pdata->hw_pdata; + + hda_sdw_int_enable(sdev, false); + + if (hdev->sdw) + sdw_intel_exit(hdev->sdw); + hdev->sdw = NULL; + + return 0; +} +#endif + /* * Debug */ @@ -342,12 +509,12 @@ static const char *fixup_tplg_name(struct snd_sof_dev *sdev, static int hda_init_caps(struct snd_sof_dev *sdev) { struct hdac_bus *bus = sof_to_bus(sdev); + struct snd_sof_pdata *pdata = sdev->pdata; + struct snd_soc_acpi_mach *mach; #if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA) struct hdac_ext_link *hlink; - struct snd_soc_acpi_mach_params *mach_params; struct snd_soc_acpi_mach *hda_mach; - struct snd_sof_pdata *pdata = sdev->pdata; - struct snd_soc_acpi_mach *mach; + struct snd_soc_acpi_mach_params *mach_params; const char *tplg_filename; const char *idisp_str; const char *dmic_str; @@ -355,6 +522,8 @@ static int hda_init_caps(struct snd_sof_dev *sdev) int codec_num = 0; int i; #endif + struct sof_intel_hda_dev *hdev = pdata->hw_pdata; + u32 link_mask; int ret = 0; device_disable_async_suspend(bus->dev); @@ -383,6 +552,59 @@ static int hda_init_caps(struct snd_sof_dev *sdev) return ret; } + /* scan SoundWire capabilities exposed by DSDT */ + ret = hda_sdw_acpi_scan(sdev); + if (ret < 0) { + dev_dbg(sdev->dev, "skipping SoundWire, ACPI scan error\n"); + goto skip_soundwire; + } + + link_mask = hdev->info.link_mask; + if (!link_mask) { + dev_dbg(sdev->dev, "skipping SoundWire, no links enabled\n"); + goto skip_soundwire; + } + + /* + * probe/allocate SoundWire resources. + * The hardware configuration takes place in hda_sdw_startup + * after power rails are enabled. + * It's entirely possible to have a mix of I2S/DMIC/SoundWire + * devices, so we allocate the resources in all cases. + */ + ret = hda_sdw_probe(sdev); + if (ret < 0) { + dev_err(sdev->dev, "error: SoundWire probe error\n"); + return ret; + } + + /* + * Select SoundWire machine driver if needed using the + * alternate tables. This case deals with SoundWire-only + * machines, for mixed cases with I2C/I2S the detection relies + * on the HID list. + */ + if (!pdata->machine) { + mach = pdata->desc->alt_machines; + while (mach && mach->link_mask && mach->link_mask != link_mask) + mach++; + if (mach && mach->link_mask) { + dev_dbg(bus->dev, + "SoundWire machine driver %s topology %s\n", + mach->drv_name, + mach->sof_tplg_filename); + pdata->machine = mach; + mach->mach_params.platform = dev_name(sdev->dev); + pdata->fw_filename = mach->sof_fw_filename; + pdata->tplg_filename = mach->sof_tplg_filename; + } else { + dev_info(sdev->dev, + "No SoundWire machine driver found\n"); + } + } + +skip_soundwire: + #if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA) if (bus->mlcap) snd_hdac_ext_bus_get_ml_capabilities(bus); @@ -695,6 +917,8 @@ int hda_dsp_remove(struct snd_sof_dev *sdev) snd_hdac_ext_bus_device_remove(bus); #endif + hda_sdw_exit(sdev); + if (!IS_ERR_OR_NULL(hda->dmic_dev)) platform_device_unregister(hda->dmic_dev); diff --git a/sound/soc/sof/intel/hda.h b/sound/soc/sof/intel/hda.h index 18d7e72bf9b722..c76d11dd48f2f6 100644 --- a/sound/soc/sof/intel/hda.h +++ b/sound/soc/sof/intel/hda.h @@ -11,6 +11,8 @@ #ifndef __SOF_INTEL_HDA_H #define __SOF_INTEL_HDA_H +#include +#include #include #include #include "shim.h" @@ -413,6 +415,12 @@ struct sof_intel_hda_dev { /* DMIC device */ struct platform_device *dmic_dev; + + /* ACPI information stored between scan and probe steps */ + struct sdw_intel_acpi_info info; + + /* sdw context allocated by SoundWire driver */ + struct sdw_intel_ctx *sdw; }; static inline struct hdac_bus *sof_to_bus(struct snd_sof_dev *s) @@ -605,6 +613,42 @@ int hda_dsp_trace_init(struct snd_sof_dev *sdev, u32 *stream_tag); int hda_dsp_trace_release(struct snd_sof_dev *sdev); int hda_dsp_trace_trigger(struct snd_sof_dev *sdev, int cmd); +/* + * SoundWire support + */ +#if IS_ENABLED(CONFIG_SND_SOC_SOF_INTEL_SOUNDWIRE) + +int hda_sdw_startup(struct snd_sof_dev *sdev); +void hda_sdw_int_enable(struct snd_sof_dev *sdev, bool enable); + +#else + +static inline int hda_sdw_acpi_scan(struct snd_sof_dev *sdev) +{ + return 0; +} + +static inline int hda_sdw_probe(struct snd_sof_dev *sdev) +{ + return 0; +} + +static inline int hda_sdw_startup(struct snd_sof_dev *sdev) +{ + return 0; +} + +static inline int hda_sdw_exit(struct snd_sof_dev *sdev) +{ + return 0; +} + +static inline void hda_sdw_int_enable(struct snd_sof_dev *sdev, bool enable) +{ +} + +#endif + /* common dai driver */ extern struct snd_soc_dai_driver skl_dai[]; diff --git a/sound/soc/sof/pcm.c b/sound/soc/sof/pcm.c index 3d5cd1b445ba0a..90e477a9a97b36 100644 --- a/sound/soc/sof/pcm.c +++ b/sound/soc/sof/pcm.c @@ -356,6 +356,9 @@ static int sof_pcm_trigger(struct snd_soc_component *component, return 0; } + dev_dbg(sdev->dev, "pcm: trigger resume stream %d dir %d cmd %d\n", + spcm->pcm.pcm_id, substream->stream, cmd); + /* set up hw_params */ ret = sof_pcm_prepare(component, substream); if (ret < 0) { @@ -389,6 +392,10 @@ static int sof_pcm_trigger(struct snd_soc_component *component, spcm->stream[substream->stream].suspend_ignored = true; return 0; } + + dev_dbg(sdev->dev, "pcm: trigger suspend stream %d dir %d cmd %d\n", + spcm->pcm.pcm_id, substream->stream, cmd); + /* fallthrough */ case SNDRV_PCM_TRIGGER_STOP: stream.hdr.cmd |= SOF_IPC_STREAM_TRIG_STOP; diff --git a/sound/soc/sof/pm.c b/sound/soc/sof/pm.c index 560a937e048420..951f9ca9fc69a2 100644 --- a/sound/soc/sof/pm.c +++ b/sound/soc/sof/pm.c @@ -17,6 +17,8 @@ static int sof_restore_kcontrols(struct snd_sof_dev *sdev) int ipc_cmd, ctrl_type; int ret = 0; + dev_err(sdev->dev, "%s\n", __func__); + /* restore kcontrol values */ list_for_each_entry(scontrol, &sdev->kcontrol_list, list) { /* reset readback offset for scontrol after resuming */ @@ -69,6 +71,8 @@ static int sof_restore_pipelines(struct snd_sof_dev *sdev) struct sof_ipc_cmd_hdr *hdr; int ret; + dev_err(sdev->dev, "%s\n", __func__); + /* restore pipeline components */ list_for_each_entry_reverse(swidget, &sdev->widget_list, list) { struct sof_ipc_comp_reply r; @@ -202,6 +206,8 @@ static int sof_send_pm_ctx_ipc(struct snd_sof_dev *sdev, int cmd) struct sof_ipc_pm_ctx pm_ctx; struct sof_ipc_reply reply; + dev_err(sdev->dev, "%s\n", __func__); + memset(&pm_ctx, 0, sizeof(pm_ctx)); /* configure ctx save ipc message */ @@ -220,6 +226,7 @@ static int sof_set_hw_params_upon_resume(struct snd_sof_dev *sdev) snd_pcm_state_t state; int dir; + dev_err(sdev->dev, "%s\n", __func__); /* * SOF requires hw_params to be set-up internally upon resume. * So, set the flag to indicate this for those streams that @@ -265,6 +272,8 @@ static int sof_resume(struct device *dev, bool runtime_resume) struct snd_sof_dev *sdev = dev_get_drvdata(dev); int ret; + dev_err(dev, "%s\n", __func__); + /* do nothing if dsp resume callbacks are not set */ if (!sof_ops(sdev)->resume || !sof_ops(sdev)->runtime_resume) return 0; @@ -337,6 +346,8 @@ static int sof_suspend(struct device *dev, bool runtime_suspend) struct snd_sof_dev *sdev = dev_get_drvdata(dev); int ret; + dev_err(dev, "%s\n", __func__); + /* do nothing if dsp suspend callback is not set */ if (!sof_ops(sdev)->suspend) return 0; @@ -393,6 +404,7 @@ static int sof_suspend(struct device *dev, bool runtime_suspend) int snd_sof_runtime_suspend(struct device *dev) { + dev_err(dev, "%s\n", __func__); return sof_suspend(dev, true); } EXPORT_SYMBOL(snd_sof_runtime_suspend); @@ -401,12 +413,14 @@ int snd_sof_runtime_idle(struct device *dev) { struct snd_sof_dev *sdev = dev_get_drvdata(dev); + dev_err(dev, "%s\n", __func__); return snd_sof_dsp_runtime_idle(sdev); } EXPORT_SYMBOL(snd_sof_runtime_idle); int snd_sof_runtime_resume(struct device *dev) { + dev_err(dev, "%s\n", __func__); return sof_resume(dev, true); } EXPORT_SYMBOL(snd_sof_runtime_resume); @@ -433,6 +447,8 @@ int snd_sof_resume(struct device *dev) struct snd_sof_dev *sdev = dev_get_drvdata(dev); int ret; + dev_err(dev, "%s\n", __func__); + if (sdev->s0_suspend) { /* resume from D0I3 */ dev_dbg(sdev->dev, "DSP will exit from D0i3...\n"); @@ -462,6 +478,8 @@ int snd_sof_suspend(struct device *dev) struct snd_sof_dev *sdev = dev_get_drvdata(dev); int ret; + dev_err(dev, "%s\n", __func__); + if (sdev->s0_suspend) { /* suspend to D0i3 */ dev_dbg(sdev->dev, "DSP is trying to enter D0i3...\n"); diff --git a/sound/soc/sof/sof-pci-dev.c b/sound/soc/sof/sof-pci-dev.c index bbeffd932de7fd..94af36149e6914 100644 --- a/sound/soc/sof/sof-pci-dev.c +++ b/sound/soc/sof/sof-pci-dev.c @@ -103,6 +103,7 @@ static const struct sof_dev_desc tng_desc = { #if IS_ENABLED(CONFIG_SND_SOC_SOF_CANNONLAKE) static const struct sof_dev_desc cnl_desc = { .machines = snd_soc_acpi_intel_cnl_machines, + .alt_machines = snd_soc_acpi_intel_cnl_sdw_machines, .resindex_lpe_base = 0, .resindex_pcicfg_base = -1, .resindex_imr_base = -1, @@ -121,6 +122,7 @@ static const struct sof_dev_desc cnl_desc = { #if IS_ENABLED(CONFIG_SND_SOC_SOF_COFFEELAKE) static const struct sof_dev_desc cfl_desc = { .machines = snd_soc_acpi_intel_cfl_machines, + .alt_machines = snd_soc_acpi_intel_cfl_sdw_machines, .resindex_lpe_base = 0, .resindex_pcicfg_base = -1, .resindex_imr_base = -1, @@ -141,6 +143,7 @@ static const struct sof_dev_desc cfl_desc = { static const struct sof_dev_desc cml_desc = { .machines = snd_soc_acpi_intel_cml_machines, + .alt_machines = snd_soc_acpi_intel_cml_sdw_machines, .resindex_lpe_base = 0, .resindex_pcicfg_base = -1, .resindex_imr_base = -1, @@ -159,6 +162,7 @@ static const struct sof_dev_desc cml_desc = { #if IS_ENABLED(CONFIG_SND_SOC_SOF_ICELAKE) static const struct sof_dev_desc icl_desc = { .machines = snd_soc_acpi_intel_icl_machines, + .alt_machines = snd_soc_acpi_intel_icl_sdw_machines, .resindex_lpe_base = 0, .resindex_pcicfg_base = -1, .resindex_imr_base = -1, @@ -177,6 +181,7 @@ static const struct sof_dev_desc icl_desc = { #if IS_ENABLED(CONFIG_SND_SOC_SOF_TIGERLAKE) static const struct sof_dev_desc tgl_desc = { .machines = snd_soc_acpi_intel_tgl_machines, + .alt_machines = snd_soc_acpi_intel_tgl_sdw_machines, .resindex_lpe_base = 0, .resindex_pcicfg_base = -1, .resindex_imr_base = -1,