From 89e590535f32d4bc548bcf266f3b046e50942f6d Mon Sep 17 00:00:00 2001 From: Sanyog Kale Date: Thu, 26 Apr 2018 18:38:08 +0530 Subject: soundwire: Add support for SoundWire stream management This patch adds APIs and relevant stream data structures for initialization and release of stream. Signed-off-by: Hardik T Shah Signed-off-by: Sanyog Kale Signed-off-by: Shreyas NC Signed-off-by: Vinod Koul --- drivers/soundwire/bus.h | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) (limited to 'drivers/soundwire/bus.h') diff --git a/drivers/soundwire/bus.h b/drivers/soundwire/bus.h index 345c34d697e9..2e5043de9a4b 100644 --- a/drivers/soundwire/bus.h +++ b/drivers/soundwire/bus.h @@ -45,6 +45,42 @@ struct sdw_msg { bool page; }; +/** + * sdw_slave_runtime: Runtime Stream parameters for Slave + * + * @slave: Slave handle + * @direction: Data direction for Slave + * @ch_count: Number of channels handled by the Slave for + * this stream + * @m_rt_node: sdw_master_runtime list node + */ +struct sdw_slave_runtime { + struct sdw_slave *slave; + enum sdw_data_direction direction; + unsigned int ch_count; + struct list_head m_rt_node; +}; + +/** + * sdw_master_runtime: Runtime stream parameters for Master + * + * @bus: Bus handle + * @stream: Stream runtime handle + * @direction: Data direction for Master + * @ch_count: Number of channels handled by the Master for + * this stream, can be zero. + * @slave_rt_list: Slave runtime list + * @bus_node: sdw_bus m_rt_list node + */ +struct sdw_master_runtime { + struct sdw_bus *bus; + struct sdw_stream_runtime *stream; + enum sdw_data_direction direction; + unsigned int ch_count; + struct list_head slave_rt_list; + struct list_head bus_node; +}; + int sdw_transfer(struct sdw_bus *bus, struct sdw_msg *msg); int sdw_transfer_defer(struct sdw_bus *bus, struct sdw_msg *msg, struct sdw_defer *defer); -- cgit v1.2.3 From bbe7379d8040a88ea832fdcbe49a9d16e9b5755e Mon Sep 17 00:00:00 2001 From: Sanyog Kale Date: Thu, 26 Apr 2018 18:38:13 +0530 Subject: soundwire: Add support for port management Add Soundwire port data structures and APIS for initialization and release of ports. Signed-off-by: Sanyog Kale Signed-off-by: Shreyas NC Signed-off-by: Vinod Koul --- drivers/soundwire/bus.h | 25 ++++++++ drivers/soundwire/stream.c | 144 +++++++++++++++++++++++++++++++++++++++++- include/linux/soundwire/sdw.h | 67 ++++++++++++++++++++ 3 files changed, 234 insertions(+), 2 deletions(-) (limited to 'drivers/soundwire/bus.h') diff --git a/drivers/soundwire/bus.h b/drivers/soundwire/bus.h index 2e5043de9a4b..39e6811e435c 100644 --- a/drivers/soundwire/bus.h +++ b/drivers/soundwire/bus.h @@ -45,6 +45,27 @@ struct sdw_msg { bool page; }; +/** + * sdw_port_runtime: Runtime port parameters for Master or Slave + * + * @num: Port number. For audio streams, valid port number ranges from + * [1,14] + * @ch_mask: Channel mask + * @transport_params: Transport parameters + * @port_params: Port parameters + * @port_node: List node for Master or Slave port_list + * + * SoundWire spec has no mention of ports for Master interface but the + * concept is logically extended. + */ +struct sdw_port_runtime { + int num; + int ch_mask; + struct sdw_transport_params transport_params; + struct sdw_port_params port_params; + struct list_head port_node; +}; + /** * sdw_slave_runtime: Runtime Stream parameters for Slave * @@ -53,12 +74,14 @@ struct sdw_msg { * @ch_count: Number of channels handled by the Slave for * this stream * @m_rt_node: sdw_master_runtime list node + * @port_list: List of Slave Ports configured for this stream */ struct sdw_slave_runtime { struct sdw_slave *slave; enum sdw_data_direction direction; unsigned int ch_count; struct list_head m_rt_node; + struct list_head port_list; }; /** @@ -70,6 +93,7 @@ struct sdw_slave_runtime { * @ch_count: Number of channels handled by the Master for * this stream, can be zero. * @slave_rt_list: Slave runtime list + * @port_list: List of Master Ports configured for this stream, can be zero. * @bus_node: sdw_bus m_rt_list node */ struct sdw_master_runtime { @@ -78,6 +102,7 @@ struct sdw_master_runtime { enum sdw_data_direction direction; unsigned int ch_count; struct list_head slave_rt_list; + struct list_head port_list; struct list_head bus_node; }; diff --git a/drivers/soundwire/stream.c b/drivers/soundwire/stream.c index 89b2550ea453..289342340262 100644 --- a/drivers/soundwire/stream.c +++ b/drivers/soundwire/stream.c @@ -81,6 +81,7 @@ static struct sdw_master_runtime return NULL; /* Initialization of Master runtime handle */ + INIT_LIST_HEAD(&m_rt->port_list); INIT_LIST_HEAD(&m_rt->slave_rt_list); stream->m_rt = m_rt; @@ -115,6 +116,7 @@ static struct sdw_slave_runtime if (!s_rt) return NULL; + INIT_LIST_HEAD(&s_rt->port_list); s_rt->ch_count = stream_config->ch_count; s_rt->direction = stream_config->direction; s_rt->slave = slave; @@ -122,6 +124,38 @@ static struct sdw_slave_runtime return s_rt; } +static void sdw_master_port_release(struct sdw_bus *bus, + struct sdw_master_runtime *m_rt) +{ + struct sdw_port_runtime *p_rt, *_p_rt; + + list_for_each_entry_safe(p_rt, _p_rt, + &m_rt->port_list, port_node) { + list_del(&p_rt->port_node); + kfree(p_rt); + } +} + +static void sdw_slave_port_release(struct sdw_bus *bus, + struct sdw_slave *slave, + struct sdw_stream_runtime *stream) +{ + struct sdw_port_runtime *p_rt, *_p_rt; + struct sdw_master_runtime *m_rt = stream->m_rt; + struct sdw_slave_runtime *s_rt; + + list_for_each_entry(s_rt, &m_rt->slave_rt_list, m_rt_node) { + if (s_rt->slave != slave) + continue; + + list_for_each_entry_safe(p_rt, _p_rt, + &s_rt->port_list, port_node) { + list_del(&p_rt->port_node); + kfree(p_rt); + } + } +} + /** * sdw_release_slave_stream() - Free Slave(s) runtime handle * @@ -176,7 +210,7 @@ static void sdw_release_master_stream(struct sdw_stream_runtime *stream) * @bus: SDW Bus instance * @stream: SoundWire stream * - * This removes and frees master_rt from a stream + * This removes and frees port_rt and master_rt from a stream */ int sdw_stream_remove_master(struct sdw_bus *bus, struct sdw_stream_runtime *stream) @@ -184,6 +218,7 @@ int sdw_stream_remove_master(struct sdw_bus *bus, mutex_lock(&bus->bus_lock); sdw_release_master_stream(stream); + sdw_master_port_release(bus, stream->m_rt); stream->state = SDW_STREAM_RELEASED; kfree(stream->m_rt); stream->m_rt = NULL; @@ -200,13 +235,14 @@ EXPORT_SYMBOL(sdw_stream_remove_master); * @slave: SDW Slave instance * @stream: SoundWire stream * - * This removes and frees slave_rt from a stream + * This removes and frees port_rt and slave_rt from a stream */ int sdw_stream_remove_slave(struct sdw_slave *slave, struct sdw_stream_runtime *stream) { mutex_lock(&slave->bus->bus_lock); + sdw_slave_port_release(slave->bus, slave, stream); sdw_release_slave_stream(slave, stream); mutex_unlock(&slave->bus->bus_lock); @@ -260,15 +296,107 @@ static int sdw_config_stream(struct device *dev, return 0; } +static int sdw_is_valid_port_range(struct device *dev, + struct sdw_port_runtime *p_rt) +{ + if (!SDW_VALID_PORT_RANGE(p_rt->num)) { + dev_err(dev, + "SoundWire: Invalid port number :%d", p_rt->num); + return -EINVAL; + } + + return 0; +} + +static struct sdw_port_runtime *sdw_port_alloc(struct device *dev, + struct sdw_port_config *port_config, + int port_index) +{ + struct sdw_port_runtime *p_rt; + + p_rt = kzalloc(sizeof(*p_rt), GFP_KERNEL); + if (!p_rt) + return NULL; + + p_rt->ch_mask = port_config[port_index].ch_mask; + p_rt->num = port_config[port_index].num; + + return p_rt; +} + +static int sdw_master_port_config(struct sdw_bus *bus, + struct sdw_master_runtime *m_rt, + struct sdw_port_config *port_config, + unsigned int num_ports) +{ + struct sdw_port_runtime *p_rt; + int i; + + /* Iterate for number of ports to perform initialization */ + for (i = 0; i < num_ports; i++) { + p_rt = sdw_port_alloc(bus->dev, port_config, i); + if (!p_rt) + return -ENOMEM; + + /* + * TODO: Check port capabilities for requested + * configuration (audio mode support) + */ + + list_add_tail(&p_rt->port_node, &m_rt->port_list); + } + + return 0; +} + +static int sdw_slave_port_config(struct sdw_slave *slave, + struct sdw_slave_runtime *s_rt, + struct sdw_port_config *port_config, + unsigned int num_config) +{ + struct sdw_port_runtime *p_rt; + int i, ret; + + /* Iterate for number of ports to perform initialization */ + for (i = 0; i < num_config; i++) { + p_rt = sdw_port_alloc(&slave->dev, port_config, i); + if (!p_rt) + return -ENOMEM; + + /* + * TODO: Check valid port range as defined by DisCo/ + * slave + */ + ret = sdw_is_valid_port_range(&slave->dev, p_rt); + if (ret < 0) { + kfree(p_rt); + return ret; + } + + /* + * TODO: Check port capabilities for requested + * configuration (audio mode support) + */ + + list_add_tail(&p_rt->port_node, &s_rt->port_list); + } + + return 0; +} + /** * sdw_stream_add_master() - Allocate and add master runtime to a stream * * @bus: SDW Bus instance * @stream_config: Stream configuration for audio stream + * @port_config: Port configuration for audio stream + * @num_ports: Number of ports * @stream: SoundWire stream */ int sdw_stream_add_master(struct sdw_bus *bus, struct sdw_stream_config *stream_config, + struct sdw_port_config *port_config, + unsigned int num_ports, struct sdw_stream_runtime *stream) { struct sdw_master_runtime *m_rt = NULL; @@ -289,6 +417,10 @@ int sdw_stream_add_master(struct sdw_bus *bus, if (ret) goto stream_error; + ret = sdw_master_port_config(bus, m_rt, port_config, num_ports); + if (ret) + goto stream_error; + stream->state = SDW_STREAM_CONFIGURED; stream_error: @@ -305,9 +437,13 @@ EXPORT_SYMBOL(sdw_stream_add_master); * @slave: SDW Slave instance * @stream_config: Stream configuration for audio stream * @stream: SoundWire stream + * @port_config: Port configuration for audio stream + * @num_ports: Number of ports */ int sdw_stream_add_slave(struct sdw_slave *slave, struct sdw_stream_config *stream_config, + struct sdw_port_config *port_config, + unsigned int num_ports, struct sdw_stream_runtime *stream) { struct sdw_slave_runtime *s_rt; @@ -344,6 +480,10 @@ int sdw_stream_add_slave(struct sdw_slave *slave, list_add_tail(&s_rt->m_rt_node, &m_rt->slave_rt_list); + ret = sdw_slave_port_config(slave, s_rt, port_config, num_ports); + if (ret) + goto stream_error; + stream->state = SDW_STREAM_CONFIGURED; goto error; diff --git a/include/linux/soundwire/sdw.h b/include/linux/soundwire/sdw.h index 610a98da103e..5e06a0de508c 100644 --- a/include/linux/soundwire/sdw.h +++ b/include/linux/soundwire/sdw.h @@ -26,6 +26,8 @@ struct sdw_slave; #define SDW_MAX_DEVICES 11 +#define SDW_VALID_PORT_RANGE(n) (n <= 14 && n >= 1) + /** * enum sdw_slave_status - Slave status * @SDW_SLAVE_UNATTACHED: Slave is not attached with the bus. @@ -430,6 +432,56 @@ int sdw_handle_slave_status(struct sdw_bus *bus, * SDW master structures and APIs */ +/** + * struct sdw_port_params: Data Port parameters + * + * @num: Port number + * @bps: Word length of the Port + * @flow_mode: Port Data flow mode + * @data_mode: Test modes or normal mode + * + * This is used to program the Data Port based on Data Port stream + * parameters. + */ +struct sdw_port_params { + unsigned int num; + unsigned int bps; + unsigned int flow_mode; + unsigned int data_mode; +}; + +/** + * struct sdw_transport_params: Data Port Transport Parameters + * + * @blk_grp_ctrl_valid: Port implements block group control + * @num: Port number + * @blk_grp_ctrl: Block group control value + * @sample_interval: Sample interval + * @offset1: Blockoffset of the payload data + * @offset2: Blockoffset of the payload data + * @hstart: Horizontal start of the payload data + * @hstop: Horizontal stop of the payload data + * @blk_pkg_mode: Block per channel or block per port + * @lane_ctrl: Data lane Port uses for Data transfer. Currently only single + * data lane is supported in bus + * + * This is used to program the Data Port based on Data Port transport + * parameters. All these parameters are banked and can be modified + * during a bank switch without any artifacts in audio stream. + */ +struct sdw_transport_params { + bool blk_grp_ctrl_valid; + unsigned int port_num; + unsigned int blk_grp_ctrl; + unsigned int sample_interval; + unsigned int offset1; + unsigned int offset2; + unsigned int hstart; + unsigned int hstop; + unsigned int blk_pkg_mode; + unsigned int lane_ctrl; +}; + struct sdw_msg; /** @@ -497,6 +549,17 @@ struct sdw_bus { int sdw_add_bus_master(struct sdw_bus *bus); void sdw_delete_bus_master(struct sdw_bus *bus); +/** + * sdw_port_config: Master or Slave Port configuration + * + * @num: Port number + * @ch_mask: channels mask for port + */ +struct sdw_port_config { + unsigned int num; + unsigned int ch_mask; +}; + /** * sdw_stream_config: Master or Slave stream configuration * @@ -569,9 +632,13 @@ struct sdw_stream_runtime *sdw_alloc_stream(char *stream_name); void sdw_release_stream(struct sdw_stream_runtime *stream); int sdw_stream_add_master(struct sdw_bus *bus, struct sdw_stream_config *stream_config, + struct sdw_port_config *port_config, + unsigned int num_ports, struct sdw_stream_runtime *stream); int sdw_stream_add_slave(struct sdw_slave *slave, struct sdw_stream_config *stream_config, + struct sdw_port_config *port_config, + unsigned int num_ports, struct sdw_stream_runtime *stream); int sdw_stream_remove_master(struct sdw_bus *bus, struct sdw_stream_runtime *stream); -- cgit v1.2.3 From f8101c74aa542b6e583513216176679a9e922b2f Mon Sep 17 00:00:00 2001 From: Sanyog Kale Date: Thu, 26 Apr 2018 18:38:17 +0530 Subject: soundwire: Add Master and Slave port programming Master and Slave port registers need to be programmed for each port used in a stream. Add the helpers for port register programming. Signed-off-by: Sanyog Kale Signed-off-by: Shreyas NC Signed-off-by: Vinod Koul --- drivers/soundwire/bus.h | 4 + drivers/soundwire/stream.c | 262 ++++++++++++++++++++++++++++++++++++++++++ include/linux/soundwire/sdw.h | 47 +++++++- 3 files changed, 312 insertions(+), 1 deletion(-) (limited to 'drivers/soundwire/bus.h') diff --git a/drivers/soundwire/bus.h b/drivers/soundwire/bus.h index 39e6811e435c..133268ab8086 100644 --- a/drivers/soundwire/bus.h +++ b/drivers/soundwire/bus.h @@ -106,6 +106,10 @@ struct sdw_master_runtime { struct list_head bus_node; }; +struct sdw_dpn_prop *sdw_get_slave_dpn_prop(struct sdw_slave *slave, + enum sdw_data_direction direction, + unsigned int port_num); + int sdw_transfer(struct sdw_bus *bus, struct sdw_msg *msg); int sdw_transfer_defer(struct sdw_bus *bus, struct sdw_msg *msg, struct sdw_defer *defer); diff --git a/drivers/soundwire/stream.c b/drivers/soundwire/stream.c index 289342340262..ea6ee96b02d2 100644 --- a/drivers/soundwire/stream.c +++ b/drivers/soundwire/stream.c @@ -11,9 +11,238 @@ #include #include #include +#include #include #include "bus.h" +static int _sdw_program_slave_port_params(struct sdw_bus *bus, + struct sdw_slave *slave, + struct sdw_transport_params *t_params, + enum sdw_dpn_type type) +{ + u32 addr1, addr2, addr3, addr4; + int ret; + u16 wbuf; + + if (bus->params.next_bank) { + addr1 = SDW_DPN_OFFSETCTRL2_B1(t_params->port_num); + addr2 = SDW_DPN_BLOCKCTRL3_B1(t_params->port_num); + addr3 = SDW_DPN_SAMPLECTRL2_B1(t_params->port_num); + addr4 = SDW_DPN_HCTRL_B1(t_params->port_num); + } else { + addr1 = SDW_DPN_OFFSETCTRL2_B0(t_params->port_num); + addr2 = SDW_DPN_BLOCKCTRL3_B0(t_params->port_num); + addr3 = SDW_DPN_SAMPLECTRL2_B0(t_params->port_num); + addr4 = SDW_DPN_HCTRL_B0(t_params->port_num); + } + + /* Program DPN_OffsetCtrl2 registers */ + ret = sdw_write(slave, addr1, t_params->offset2); + if (ret < 0) { + dev_err(bus->dev, "DPN_OffsetCtrl2 register write failed"); + return ret; + } + + /* Program DPN_BlockCtrl3 register */ + ret = sdw_write(slave, addr2, t_params->blk_pkg_mode); + if (ret < 0) { + dev_err(bus->dev, "DPN_BlockCtrl3 register write failed"); + return ret; + } + + /* + * Data ports are FULL, SIMPLE and REDUCED. This function handles + * FULL and REDUCED only and and beyond this point only FULL is + * handled, so bail out if we are not FULL data port type + */ + if (type != SDW_DPN_FULL) + return ret; + + /* Program DPN_SampleCtrl2 register */ + wbuf = (t_params->sample_interval - 1); + wbuf &= SDW_DPN_SAMPLECTRL_HIGH; + wbuf >>= SDW_REG_SHIFT(SDW_DPN_SAMPLECTRL_HIGH); + + ret = sdw_write(slave, addr3, wbuf); + if (ret < 0) { + dev_err(bus->dev, "DPN_SampleCtrl2 register write failed"); + return ret; + } + + /* Program DPN_HCtrl register */ + wbuf = t_params->hstart; + wbuf <<= SDW_REG_SHIFT(SDW_DPN_HCTRL_HSTART); + wbuf |= t_params->hstop; + + ret = sdw_write(slave, addr4, wbuf); + if (ret < 0) + dev_err(bus->dev, "DPN_HCtrl register write failed"); + + return ret; +} + +static int sdw_program_slave_port_params(struct sdw_bus *bus, + struct sdw_slave_runtime *s_rt, + struct sdw_port_runtime *p_rt) +{ + struct sdw_transport_params *t_params = &p_rt->transport_params; + struct sdw_port_params *p_params = &p_rt->port_params; + struct sdw_slave_prop *slave_prop = &s_rt->slave->prop; + u32 addr1, addr2, addr3, addr4, addr5, addr6; + struct sdw_dpn_prop *dpn_prop; + int ret; + u8 wbuf; + + dpn_prop = sdw_get_slave_dpn_prop(s_rt->slave, + s_rt->direction, + t_params->port_num); + if (!dpn_prop) + return -EINVAL; + + addr1 = SDW_DPN_PORTCTRL(t_params->port_num); + addr2 = SDW_DPN_BLOCKCTRL1(t_params->port_num); + + if (bus->params.next_bank) { + addr3 = SDW_DPN_SAMPLECTRL1_B1(t_params->port_num); + addr4 = SDW_DPN_OFFSETCTRL1_B1(t_params->port_num); + addr5 = SDW_DPN_BLOCKCTRL2_B1(t_params->port_num); + addr6 = SDW_DPN_LANECTRL_B1(t_params->port_num); + + } else { + addr3 = SDW_DPN_SAMPLECTRL1_B0(t_params->port_num); + addr4 = SDW_DPN_OFFSETCTRL1_B0(t_params->port_num); + addr5 = SDW_DPN_BLOCKCTRL2_B0(t_params->port_num); + addr6 = SDW_DPN_LANECTRL_B0(t_params->port_num); + } + + /* Program DPN_PortCtrl register */ + wbuf = p_params->data_mode << SDW_REG_SHIFT(SDW_DPN_PORTCTRL_DATAMODE); + wbuf |= p_params->flow_mode; + + ret = sdw_update(s_rt->slave, addr1, 0xF, wbuf); + if (ret < 0) { + dev_err(&s_rt->slave->dev, + "DPN_PortCtrl register write failed for port %d", + t_params->port_num); + return ret; + } + + /* Program DPN_BlockCtrl1 register */ + ret = sdw_write(s_rt->slave, addr2, (p_params->bps - 1)); + if (ret < 0) { + dev_err(&s_rt->slave->dev, + "DPN_BlockCtrl1 register write failed for port %d", + t_params->port_num); + return ret; + } + + /* Program DPN_SampleCtrl1 register */ + wbuf = (t_params->sample_interval - 1) & SDW_DPN_SAMPLECTRL_LOW; + ret = sdw_write(s_rt->slave, addr3, wbuf); + if (ret < 0) { + dev_err(&s_rt->slave->dev, + "DPN_SampleCtrl1 register write failed for port %d", + t_params->port_num); + return ret; + } + + /* Program DPN_OffsetCtrl1 registers */ + ret = sdw_write(s_rt->slave, addr4, t_params->offset1); + if (ret < 0) { + dev_err(&s_rt->slave->dev, + "DPN_OffsetCtrl1 register write failed for port %d", + t_params->port_num); + return ret; + } + + /* Program DPN_BlockCtrl2 register*/ + if (t_params->blk_grp_ctrl_valid) { + ret = sdw_write(s_rt->slave, addr5, t_params->blk_grp_ctrl); + if (ret < 0) { + dev_err(&s_rt->slave->dev, + "DPN_BlockCtrl2 reg write failed for port %d", + t_params->port_num); + return ret; + } + } + + /* program DPN_LaneCtrl register */ + if (slave_prop->lane_control_support) { + ret = sdw_write(s_rt->slave, addr6, t_params->lane_ctrl); + if (ret < 0) { + dev_err(&s_rt->slave->dev, + "DPN_LaneCtrl register write failed for port %d", + t_params->port_num); + return ret; + } + } + + if (dpn_prop->type != SDW_DPN_SIMPLE) { + ret = _sdw_program_slave_port_params(bus, s_rt->slave, + t_params, dpn_prop->type); + if (ret < 0) + dev_err(&s_rt->slave->dev, + "Transport reg write failed for port: %d", + t_params->port_num); + } + + return ret; +} + +static int sdw_program_master_port_params(struct sdw_bus *bus, + struct sdw_port_runtime *p_rt) +{ + int ret; + + /* + * we need to set transport and port parameters for the port. + * Transport parameters refers to the smaple interval, offsets and + * hstart/stop etc of the data. Port parameters refers to word + * length, flow mode etc of the port + */ + ret = bus->port_ops->dpn_set_port_transport_params(bus, + &p_rt->transport_params, + bus->params.next_bank); + if (ret < 0) + return ret; + + return bus->port_ops->dpn_set_port_params(bus, + &p_rt->port_params, + bus->params.next_bank); +} + +/** + * sdw_program_port_params() - Programs transport parameters of Master(s) + * and Slave(s) + * + * @m_rt: Master stream runtime + */ +static int sdw_program_port_params(struct sdw_master_runtime *m_rt) +{ + struct sdw_slave_runtime *s_rt = NULL; + struct sdw_bus *bus = m_rt->bus; + struct sdw_port_runtime *p_rt; + int ret = 0; + + /* Program transport & port parameters for Slave(s) */ + list_for_each_entry(s_rt, &m_rt->slave_rt_list, m_rt_node) { + list_for_each_entry(p_rt, &s_rt->port_list, port_node) { + ret = sdw_program_slave_port_params(bus, s_rt, p_rt); + if (ret < 0) + return ret; + } + } + + /* Program transport & port parameters for Master(s) */ + list_for_each_entry(p_rt, &m_rt->port_list, port_node) { + ret = sdw_program_master_port_params(bus, p_rt); + if (ret < 0) + return ret; + } + + return 0; +} + /** * sdw_release_stream() - Free the assigned stream runtime * @@ -498,3 +727,36 @@ error: return ret; } EXPORT_SYMBOL(sdw_stream_add_slave); + +/** + * sdw_get_slave_dpn_prop() - Get Slave port capabilities + * + * @slave: Slave handle + * @direction: Data direction. + * @port_num: Port number + */ +struct sdw_dpn_prop *sdw_get_slave_dpn_prop(struct sdw_slave *slave, + enum sdw_data_direction direction, + unsigned int port_num) +{ + struct sdw_dpn_prop *dpn_prop; + u8 num_ports; + int i; + + if (direction == SDW_DATA_DIR_TX) { + num_ports = hweight32(slave->prop.source_ports); + dpn_prop = slave->prop.src_dpn_prop; + } else { + num_ports = hweight32(slave->prop.sink_ports); + dpn_prop = slave->prop.sink_dpn_prop; + } + + for (i = 0; i < num_ports; i++) { + dpn_prop = &dpn_prop[i]; + + if (dpn_prop->num == port_num) + return &dpn_prop[i]; + } + + return NULL; +} diff --git a/include/linux/soundwire/sdw.h b/include/linux/soundwire/sdw.h index 5e06a0de508c..03b55804d830 100644 --- a/include/linux/soundwire/sdw.h +++ b/include/linux/soundwire/sdw.h @@ -367,7 +367,30 @@ struct sdw_slave_intr_status { }; /** - * struct sdw_slave_ops - Slave driver callback ops + * sdw_reg_bank - SoundWire register banks + * @SDW_BANK0: Soundwire register bank 0 + * @SDW_BANK1: Soundwire register bank 1 + */ +enum sdw_reg_bank { + SDW_BANK0, + SDW_BANK1, +}; + +/** + * struct sdw_bus_params: Structure holding bus configuration + * + * @curr_bank: Current bank in use (BANK0/BANK1) + * @next_bank: Next bank to use (BANK0/BANK1). next_bank will always be + * set to !curr_bank + */ +struct sdw_bus_params { + enum sdw_reg_bank curr_bank; + enum sdw_reg_bank next_bank; +}; + +/** + * struct sdw_slave_ops: Slave driver callback ops + * * @read_prop: Read Slave properties * @interrupt_callback: Device interrupt notification (invoked in thread * context) @@ -482,6 +505,24 @@ struct sdw_transport_params { unsigned int lane_ctrl; }; +/** + * struct sdw_master_port_ops: Callback functions from bus to Master + * driver to set Master Data ports. + * + * @dpn_set_port_params: Set the Port parameters for the Master Port. + * Mandatory callback + * @dpn_set_port_transport_params: Set transport parameters for the Master + * Port. Mandatory callback + */ +struct sdw_master_port_ops { + int (*dpn_set_port_params)(struct sdw_bus *bus, + struct sdw_port_params *port_params, + unsigned int bank); + int (*dpn_set_port_transport_params)(struct sdw_bus *bus, + struct sdw_transport_params *transport_params, + enum sdw_reg_bank bank); +}; + struct sdw_msg; /** @@ -525,6 +566,8 @@ struct sdw_master_ops { * @bus_lock: bus lock * @msg_lock: message lock * @ops: Master callback ops + * @port_ops: Master port callback ops + * @params: Current bus parameters * @prop: Master properties * @m_rt_list: List of Master instance of all stream(s) running on Bus. This * is used to compute and program bus bandwidth, clock, frame shape, @@ -540,6 +583,8 @@ struct sdw_bus { struct mutex bus_lock; struct mutex msg_lock; const struct sdw_master_ops *ops; + const struct sdw_master_port_ops *port_ops; + struct sdw_bus_params params; struct sdw_master_prop prop; struct list_head m_rt_list; struct sdw_defer defer_msg; -- cgit v1.2.3 From 79df15b7d37c80aee5dff361841500c79e62b1e0 Mon Sep 17 00:00:00 2001 From: Sanyog Kale Date: Thu, 26 Apr 2018 18:38:23 +0530 Subject: soundwire: Add helpers for ports operations Add helpers to configure, prepare, enable, disable and de-prepare ports. Signed-off-by: Sanyog Kale Signed-off-by: Shreyas NC Signed-off-by: Vinod Koul --- drivers/soundwire/bus.c | 26 ++++ drivers/soundwire/bus.h | 2 + drivers/soundwire/stream.c | 271 ++++++++++++++++++++++++++++++++++++++++++ include/linux/soundwire/sdw.h | 54 +++++++++ 4 files changed, 353 insertions(+) (limited to 'drivers/soundwire/bus.h') diff --git a/drivers/soundwire/bus.c b/drivers/soundwire/bus.c index abf046f6b188..b8c93f0ac0a0 100644 --- a/drivers/soundwire/bus.c +++ b/drivers/soundwire/bus.c @@ -577,6 +577,32 @@ static void sdw_modify_slave_status(struct sdw_slave *slave, mutex_unlock(&slave->bus->bus_lock); } +int sdw_configure_dpn_intr(struct sdw_slave *slave, + int port, bool enable, int mask) +{ + u32 addr; + int ret; + u8 val = 0; + + addr = SDW_DPN_INTMASK(port); + + /* Set/Clear port ready interrupt mask */ + if (enable) { + val |= mask; + val |= SDW_DPN_INT_PORT_READY; + } else { + val &= ~(mask); + val &= ~SDW_DPN_INT_PORT_READY; + } + + ret = sdw_update(slave, addr, (mask | SDW_DPN_INT_PORT_READY), val); + if (ret < 0) + dev_err(slave->bus->dev, + "SDW_DPN_INTMASK write failed:%d", val); + + return ret; +} + static int sdw_initialize_slave(struct sdw_slave *slave) { struct sdw_slave_prop *prop = &slave->prop; diff --git a/drivers/soundwire/bus.h b/drivers/soundwire/bus.h index 133268ab8086..07da6c4b10c4 100644 --- a/drivers/soundwire/bus.h +++ b/drivers/soundwire/bus.h @@ -109,6 +109,8 @@ struct sdw_master_runtime { struct sdw_dpn_prop *sdw_get_slave_dpn_prop(struct sdw_slave *slave, enum sdw_data_direction direction, unsigned int port_num); +int sdw_configure_dpn_intr(struct sdw_slave *slave, int port, + bool enable, int mask); int sdw_transfer(struct sdw_bus *bus, struct sdw_msg *msg); int sdw_transfer_defer(struct sdw_bus *bus, struct sdw_msg *msg, diff --git a/drivers/soundwire/stream.c b/drivers/soundwire/stream.c index ea6ee96b02d2..7acb4c59f208 100644 --- a/drivers/soundwire/stream.c +++ b/drivers/soundwire/stream.c @@ -243,6 +243,277 @@ static int sdw_program_port_params(struct sdw_master_runtime *m_rt) return 0; } +/** + * sdw_enable_disable_slave_ports: Enable/disable slave data port + * + * @bus: bus instance + * @s_rt: slave runtime + * @p_rt: port runtime + * @en: enable or disable operation + * + * This function only sets the enable/disable bits in the relevant bank, the + * actual enable/disable is done with a bank switch + */ +static int sdw_enable_disable_slave_ports(struct sdw_bus *bus, + struct sdw_slave_runtime *s_rt, + struct sdw_port_runtime *p_rt, bool en) +{ + struct sdw_transport_params *t_params = &p_rt->transport_params; + u32 addr; + int ret; + + if (bus->params.next_bank) + addr = SDW_DPN_CHANNELEN_B1(p_rt->num); + else + addr = SDW_DPN_CHANNELEN_B0(p_rt->num); + + /* + * Since bus doesn't support sharing a port across two streams, + * it is safe to reset this register + */ + if (en) + ret = sdw_update(s_rt->slave, addr, 0xFF, p_rt->ch_mask); + else + ret = sdw_update(s_rt->slave, addr, 0xFF, 0x0); + + if (ret < 0) + dev_err(&s_rt->slave->dev, + "Slave chn_en reg write failed:%d port:%d", + ret, t_params->port_num); + + return ret; +} + +static int sdw_enable_disable_master_ports(struct sdw_master_runtime *m_rt, + struct sdw_port_runtime *p_rt, bool en) +{ + struct sdw_transport_params *t_params = &p_rt->transport_params; + struct sdw_bus *bus = m_rt->bus; + struct sdw_enable_ch enable_ch; + int ret = 0; + + enable_ch.port_num = p_rt->num; + enable_ch.ch_mask = p_rt->ch_mask; + enable_ch.enable = en; + + /* Perform Master port channel(s) enable/disable */ + if (bus->port_ops->dpn_port_enable_ch) { + ret = bus->port_ops->dpn_port_enable_ch(bus, + &enable_ch, bus->params.next_bank); + if (ret < 0) { + dev_err(bus->dev, + "Master chn_en write failed:%d port:%d", + ret, t_params->port_num); + return ret; + } + } else { + dev_err(bus->dev, + "dpn_port_enable_ch not supported, %s failed\n", + en ? "enable" : "disable"); + return -EINVAL; + } + + return 0; +} + +/** + * sdw_enable_disable_ports() - Enable/disable port(s) for Master and + * Slave(s) + * + * @m_rt: Master stream runtime + * @en: mode (enable/disable) + */ +static int sdw_enable_disable_ports(struct sdw_master_runtime *m_rt, bool en) +{ + struct sdw_port_runtime *s_port, *m_port; + struct sdw_slave_runtime *s_rt = NULL; + int ret = 0; + + /* Enable/Disable Slave port(s) */ + list_for_each_entry(s_rt, &m_rt->slave_rt_list, m_rt_node) { + list_for_each_entry(s_port, &s_rt->port_list, port_node) { + ret = sdw_enable_disable_slave_ports(m_rt->bus, s_rt, + s_port, en); + if (ret < 0) + return ret; + } + } + + /* Enable/Disable Master port(s) */ + list_for_each_entry(m_port, &m_rt->port_list, port_node) { + ret = sdw_enable_disable_master_ports(m_rt, m_port, en); + if (ret < 0) + return ret; + } + + return 0; +} + +static int sdw_do_port_prep(struct sdw_slave_runtime *s_rt, + struct sdw_prepare_ch prep_ch, enum sdw_port_prep_ops cmd) +{ + const struct sdw_slave_ops *ops = s_rt->slave->ops; + int ret; + + if (ops->port_prep) { + ret = ops->port_prep(s_rt->slave, &prep_ch, cmd); + if (ret < 0) { + dev_err(&s_rt->slave->dev, + "Slave Port Prep cmd %d failed: %d", cmd, ret); + return ret; + } + } + + return 0; +} + +static int sdw_prep_deprep_slave_ports(struct sdw_bus *bus, + struct sdw_slave_runtime *s_rt, + struct sdw_port_runtime *p_rt, bool prep) +{ + struct completion *port_ready = NULL; + struct sdw_dpn_prop *dpn_prop; + struct sdw_prepare_ch prep_ch; + unsigned int time_left; + bool intr = false; + int ret = 0, val; + u32 addr; + + prep_ch.num = p_rt->num; + prep_ch.ch_mask = p_rt->ch_mask; + + dpn_prop = sdw_get_slave_dpn_prop(s_rt->slave, + s_rt->direction, + prep_ch.num); + if (!dpn_prop) { + dev_err(bus->dev, + "Slave Port:%d properties not found", prep_ch.num); + return -EINVAL; + } + + prep_ch.prepare = prep; + + prep_ch.bank = bus->params.next_bank; + + if (dpn_prop->device_interrupts || !dpn_prop->simple_ch_prep_sm) + intr = true; + + /* + * Enable interrupt before Port prepare. + * For Port de-prepare, it is assumed that port + * was prepared earlier + */ + if (prep && intr) { + ret = sdw_configure_dpn_intr(s_rt->slave, p_rt->num, prep, + dpn_prop->device_interrupts); + if (ret < 0) + return ret; + } + + /* Inform slave about the impending port prepare */ + sdw_do_port_prep(s_rt, prep_ch, SDW_OPS_PORT_PRE_PREP); + + /* Prepare Slave port implementing CP_SM */ + if (!dpn_prop->simple_ch_prep_sm) { + addr = SDW_DPN_PREPARECTRL(p_rt->num); + + if (prep) + ret = sdw_update(s_rt->slave, addr, + 0xFF, p_rt->ch_mask); + else + ret = sdw_update(s_rt->slave, addr, 0xFF, 0x0); + + if (ret < 0) { + dev_err(&s_rt->slave->dev, + "Slave prep_ctrl reg write failed"); + return ret; + } + + /* Wait for completion on port ready */ + port_ready = &s_rt->slave->port_ready[prep_ch.num]; + time_left = wait_for_completion_timeout(port_ready, + msecs_to_jiffies(dpn_prop->ch_prep_timeout)); + + val = sdw_read(s_rt->slave, SDW_DPN_PREPARESTATUS(p_rt->num)); + val &= p_rt->ch_mask; + if (!time_left || val) { + dev_err(&s_rt->slave->dev, + "Chn prep failed for port:%d", prep_ch.num); + return -ETIMEDOUT; + } + } + + /* Inform slaves about ports prepared */ + sdw_do_port_prep(s_rt, prep_ch, SDW_OPS_PORT_POST_PREP); + + /* Disable interrupt after Port de-prepare */ + if (!prep && intr) + ret = sdw_configure_dpn_intr(s_rt->slave, p_rt->num, prep, + dpn_prop->device_interrupts); + + return ret; +} + +static int sdw_prep_deprep_master_ports(struct sdw_master_runtime *m_rt, + struct sdw_port_runtime *p_rt, bool prep) +{ + struct sdw_transport_params *t_params = &p_rt->transport_params; + struct sdw_bus *bus = m_rt->bus; + const struct sdw_master_port_ops *ops = bus->port_ops; + struct sdw_prepare_ch prep_ch; + int ret = 0; + + prep_ch.num = p_rt->num; + prep_ch.ch_mask = p_rt->ch_mask; + prep_ch.prepare = prep; /* Prepare/De-prepare */ + prep_ch.bank = bus->params.next_bank; + + /* Pre-prepare/Pre-deprepare port(s) */ + if (ops->dpn_port_prep) { + ret = ops->dpn_port_prep(bus, &prep_ch); + if (ret < 0) { + dev_err(bus->dev, "Port prepare failed for port:%d", + t_params->port_num); + return ret; + } + } + + return ret; +} + +/** + * sdw_prep_deprep_ports() - Prepare/De-prepare port(s) for Master(s) and + * Slave(s) + * + * @m_rt: Master runtime handle + * @prep: Prepare or De-prepare + */ +static int sdw_prep_deprep_ports(struct sdw_master_runtime *m_rt, bool prep) +{ + struct sdw_slave_runtime *s_rt = NULL; + struct sdw_port_runtime *p_rt; + int ret = 0; + + /* Prepare/De-prepare Slave port(s) */ + list_for_each_entry(s_rt, &m_rt->slave_rt_list, m_rt_node) { + list_for_each_entry(p_rt, &s_rt->port_list, port_node) { + ret = sdw_prep_deprep_slave_ports(m_rt->bus, s_rt, + p_rt, prep); + if (ret < 0) + return ret; + } + } + + /* Prepare/De-prepare Master port(s) */ + list_for_each_entry(p_rt, &m_rt->port_list, port_node) { + ret = sdw_prep_deprep_master_ports(m_rt, p_rt, prep); + if (ret < 0) + return ret; + } + + return ret; +} + /** * sdw_release_stream() - Free the assigned stream runtime * diff --git a/include/linux/soundwire/sdw.h b/include/linux/soundwire/sdw.h index 03b55804d830..fab7de3c00f1 100644 --- a/include/linux/soundwire/sdw.h +++ b/include/linux/soundwire/sdw.h @@ -376,6 +376,37 @@ enum sdw_reg_bank { SDW_BANK1, }; +/** + * struct sdw_prepare_ch: Prepare/De-prepare Data Port channel + * + * @num: Port number + * @ch_mask: Active channel mask + * @prepare: Prepare (true) /de-prepare (false) channel + * @bank: Register bank, which bank Slave/Master driver should program for + * implementation defined registers. This is always updated to next_bank + * value read from bus params. + * + */ +struct sdw_prepare_ch { + unsigned int num; + unsigned int ch_mask; + bool prepare; + unsigned int bank; +}; + +/** + * enum sdw_port_prep_ops: Prepare operations for Data Port + * + * @SDW_OPS_PORT_PRE_PREP: Pre prepare operation for the Port + * @SDW_OPS_PORT_PREP: Prepare operation for the Port + * @SDW_OPS_PORT_POST_PREP: Post prepare operation for the Port + */ +enum sdw_port_prep_ops { + SDW_OPS_PORT_PRE_PREP = 0, + SDW_OPS_PORT_PREP = 1, + SDW_OPS_PORT_POST_PREP = 2, +}; + /** * struct sdw_bus_params: Structure holding bus configuration * @@ -395,6 +426,7 @@ struct sdw_bus_params { * @interrupt_callback: Device interrupt notification (invoked in thread * context) * @update_status: Update Slave status + * @port_prep: Prepare the port with parameters */ struct sdw_slave_ops { int (*read_prop)(struct sdw_slave *sdw); @@ -402,6 +434,9 @@ struct sdw_slave_ops { struct sdw_slave_intr_status *status); int (*update_status)(struct sdw_slave *slave, enum sdw_slave_status status); + int (*port_prep)(struct sdw_slave *slave, + struct sdw_prepare_ch *prepare_ch, + enum sdw_port_prep_ops pre_ops); }; /** @@ -505,6 +540,19 @@ struct sdw_transport_params { unsigned int lane_ctrl; }; +/** + * struct sdw_enable_ch: Enable/disable Data Port channel + * + * @num: Port number + * @ch_mask: Active channel mask + * @enable: Enable (true) /disable (false) channel + */ +struct sdw_enable_ch { + unsigned int port_num; + unsigned int ch_mask; + bool enable; +}; + /** * struct sdw_master_port_ops: Callback functions from bus to Master * driver to set Master Data ports. @@ -513,6 +561,8 @@ struct sdw_transport_params { * Mandatory callback * @dpn_set_port_transport_params: Set transport parameters for the Master * Port. Mandatory callback + * @dpn_port_prep: Port prepare operations for the Master Data Port. + * @dpn_port_enable_ch: Enable the channels of Master Port. */ struct sdw_master_port_ops { int (*dpn_set_port_params)(struct sdw_bus *bus, @@ -521,6 +571,10 @@ struct sdw_master_port_ops { int (*dpn_set_port_transport_params)(struct sdw_bus *bus, struct sdw_transport_params *transport_params, enum sdw_reg_bank bank); + int (*dpn_port_prep)(struct sdw_bus *bus, + struct sdw_prepare_ch *prepare_ch); + int (*dpn_port_enable_ch)(struct sdw_bus *bus, + struct sdw_enable_ch *enable_ch, unsigned int bank); }; struct sdw_msg; -- cgit v1.2.3 From 99b8a5d608a64254f22483f6b67a98f26d3eac8c Mon Sep 17 00:00:00 2001 From: Sanyog Kale Date: Thu, 26 Apr 2018 18:38:28 +0530 Subject: soundwire: Add bank switch routine SoundWire supports two registers banks. So, program the alternate bank with new configuration and then performs bank switch. Signed-off-by: Sanyog Kale Signed-off-by: Shreyas NC Signed-off-by: Vinod Koul --- drivers/soundwire/bus.c | 7 ++ drivers/soundwire/bus.h | 5 ++ drivers/soundwire/stream.c | 202 ++++++++++++++++++++++++++++++++++++++++++ include/linux/soundwire/sdw.h | 48 ++++++++++ 4 files changed, 262 insertions(+) (limited to 'drivers/soundwire/bus.h') diff --git a/drivers/soundwire/bus.c b/drivers/soundwire/bus.c index b8c93f0ac0a0..084bf71b2b87 100644 --- a/drivers/soundwire/bus.c +++ b/drivers/soundwire/bus.c @@ -78,6 +78,13 @@ int sdw_add_bus_master(struct sdw_bus *bus) return ret; } + /* + * Default active bank will be 0 as out of reset the Slaves have + * to start with bank 0 (Table 40 of Spec) + */ + bus->params.curr_bank = SDW_BANK0; + bus->params.next_bank = SDW_BANK1; + return 0; } EXPORT_SYMBOL(sdw_add_bus_master); diff --git a/drivers/soundwire/bus.h b/drivers/soundwire/bus.h index 07da6c4b10c4..3b15c4e25a3a 100644 --- a/drivers/soundwire/bus.h +++ b/drivers/soundwire/bus.h @@ -45,6 +45,11 @@ struct sdw_msg { bool page; }; +#define SDW_DOUBLE_RATE_FACTOR 2 + +extern int rows[SDW_FRAME_ROWS]; +extern int cols[SDW_FRAME_COLS]; + /** * sdw_port_runtime: Runtime port parameters for Master or Slave * diff --git a/drivers/soundwire/stream.c b/drivers/soundwire/stream.c index 7acb4c59f208..bcc922062e35 100644 --- a/drivers/soundwire/stream.c +++ b/drivers/soundwire/stream.c @@ -15,6 +15,43 @@ #include #include "bus.h" +/* + * Array of supported rows and columns as per MIPI SoundWire Specification 1.1 + * + * The rows are arranged as per the array index value programmed + * in register. The index 15 has dummy value 0 in order to fill hole. + */ +int rows[SDW_FRAME_ROWS] = {48, 50, 60, 64, 75, 80, 125, 147, + 96, 100, 120, 128, 150, 160, 250, 0, + 192, 200, 240, 256, 72, 144, 90, 180}; + +int cols[SDW_FRAME_COLS] = {2, 4, 6, 8, 10, 12, 14, 16}; + +static int sdw_find_col_index(int col) +{ + int i; + + for (i = 0; i < SDW_FRAME_COLS; i++) { + if (cols[i] == col) + return i; + } + + pr_warn("Requested column not found, selecting lowest column no: 2\n"); + return 0; +} + +static int sdw_find_row_index(int row) +{ + int i; + + for (i = 0; i < SDW_FRAME_ROWS; i++) { + if (rows[i] == row) + return i; + } + + pr_warn("Requested row not found, selecting lowest row no: 48\n"); + return 0; +} static int _sdw_program_slave_port_params(struct sdw_bus *bus, struct sdw_slave *slave, struct sdw_transport_params *t_params, @@ -514,6 +551,171 @@ static int sdw_prep_deprep_ports(struct sdw_master_runtime *m_rt, bool prep) return ret; } +/** + * sdw_notify_config() - Notify bus configuration + * + * @m_rt: Master runtime handle + * + * This function notifies the Master(s) and Slave(s) of the + * new bus configuration. + */ +static int sdw_notify_config(struct sdw_master_runtime *m_rt) +{ + struct sdw_slave_runtime *s_rt; + struct sdw_bus *bus = m_rt->bus; + struct sdw_slave *slave; + int ret = 0; + + if (bus->ops->set_bus_conf) { + ret = bus->ops->set_bus_conf(bus, &bus->params); + if (ret < 0) + return ret; + } + + list_for_each_entry(s_rt, &m_rt->slave_rt_list, m_rt_node) { + slave = s_rt->slave; + + if (slave->ops->bus_config) { + ret = slave->ops->bus_config(slave, &bus->params); + if (ret < 0) + dev_err(bus->dev, "Notify Slave: %d failed", + slave->dev_num); + return ret; + } + } + + return ret; +} + +/** + * sdw_program_params() - Program transport and port parameters for Master(s) + * and Slave(s) + * + * @bus: SDW bus instance + */ +static int sdw_program_params(struct sdw_bus *bus) +{ + struct sdw_master_runtime *m_rt = NULL; + int ret = 0; + + list_for_each_entry(m_rt, &bus->m_rt_list, bus_node) { + ret = sdw_program_port_params(m_rt); + if (ret < 0) { + dev_err(bus->dev, + "Program transport params failed: %d", ret); + return ret; + } + + ret = sdw_notify_config(m_rt); + if (ret < 0) { + dev_err(bus->dev, "Notify bus config failed: %d", ret); + return ret; + } + + /* Enable port(s) on alternate bank for all active streams */ + if (m_rt->stream->state != SDW_STREAM_ENABLED) + continue; + + ret = sdw_enable_disable_ports(m_rt, true); + if (ret < 0) { + dev_err(bus->dev, "Enable channel failed: %d", ret); + return ret; + } + } + + return ret; +} + +static int sdw_bank_switch(struct sdw_bus *bus) +{ + int col_index, row_index; + struct sdw_msg *wr_msg; + u8 *wbuf = NULL; + int ret = 0; + u16 addr; + + wr_msg = kzalloc(sizeof(*wr_msg), GFP_KERNEL); + if (!wr_msg) + return -ENOMEM; + + wbuf = kzalloc(sizeof(*wbuf), GFP_KERNEL); + if (!wbuf) { + ret = -ENOMEM; + goto error_1; + } + + /* Get row and column index to program register */ + col_index = sdw_find_col_index(bus->params.col); + row_index = sdw_find_row_index(bus->params.row); + wbuf[0] = col_index | (row_index << 3); + + if (bus->params.next_bank) + addr = SDW_SCP_FRAMECTRL_B1; + else + addr = SDW_SCP_FRAMECTRL_B0; + + sdw_fill_msg(wr_msg, NULL, addr, 1, SDW_BROADCAST_DEV_NUM, + SDW_MSG_FLAG_WRITE, wbuf); + wr_msg->ssp_sync = true; + + ret = sdw_transfer(bus, wr_msg); + if (ret < 0) { + dev_err(bus->dev, "Slave frame_ctrl reg write failed"); + goto error; + } + + kfree(wr_msg); + kfree(wbuf); + bus->defer_msg.msg = NULL; + bus->params.curr_bank = !bus->params.curr_bank; + bus->params.next_bank = !bus->params.next_bank; + + return 0; + +error: + kfree(wbuf); +error_1: + kfree(wr_msg); + return ret; +} + +static int do_bank_switch(struct sdw_stream_runtime *stream) +{ + struct sdw_master_runtime *m_rt = stream->m_rt; + const struct sdw_master_ops *ops; + struct sdw_bus *bus = m_rt->bus; + int ret = 0; + + ops = bus->ops; + + /* Pre-bank switch */ + if (ops->pre_bank_switch) { + ret = ops->pre_bank_switch(bus); + if (ret < 0) { + dev_err(bus->dev, "Pre bank switch op failed: %d", ret); + return ret; + } + } + + /* Bank switch */ + ret = sdw_bank_switch(bus); + if (ret < 0) { + dev_err(bus->dev, "Bank switch failed: %d", ret); + return ret; + } + + /* Post-bank switch */ + if (ops->post_bank_switch) { + ret = ops->post_bank_switch(bus); + if (ret < 0) { + dev_err(bus->dev, + "Post bank switch op failed: %d", ret); + } + } + + return ret; +} + /** * sdw_release_stream() - Free the assigned stream runtime * diff --git a/include/linux/soundwire/sdw.h b/include/linux/soundwire/sdw.h index fab7de3c00f1..61d671271592 100644 --- a/include/linux/soundwire/sdw.h +++ b/include/linux/soundwire/sdw.h @@ -23,7 +23,17 @@ struct sdw_slave; #define SDW_MASTER_DEV_NUM 14 #define SDW_NUM_DEV_ID_REGISTERS 6 +/* frame shape defines */ +/* + * Note: The maximum row define in SoundWire spec 1.1 is 23. In order to + * fill hole with 0, one more dummy entry is added + */ +#define SDW_FRAME_ROWS 24 +#define SDW_FRAME_COLS 8 +#define SDW_FRAME_ROW_COLS (SDW_FRAME_ROWS * SDW_FRAME_COLS) + +#define SDW_FRAME_CTRL_BITS 48 #define SDW_MAX_DEVICES 11 #define SDW_VALID_PORT_RANGE(n) (n <= 14 && n >= 1) @@ -376,6 +386,21 @@ enum sdw_reg_bank { SDW_BANK1, }; +/** + * struct sdw_bus_conf: Bus configuration + * + * @clk_freq: Clock frequency, in Hz + * @num_rows: Number of rows in frame + * @num_cols: Number of columns in frame + * @bank: Next register bank + */ +struct sdw_bus_conf { + unsigned int clk_freq; + unsigned int num_rows; + unsigned int num_cols; + unsigned int bank; +}; + /** * struct sdw_prepare_ch: Prepare/De-prepare Data Port channel * @@ -413,10 +438,20 @@ enum sdw_port_prep_ops { * @curr_bank: Current bank in use (BANK0/BANK1) * @next_bank: Next bank to use (BANK0/BANK1). next_bank will always be * set to !curr_bank + * @max_dr_freq: Maximum double rate clock frequency supported, in Hz + * @curr_dr_freq: Current double rate clock frequency, in Hz + * @bandwidth: Current bandwidth + * @col: Active columns + * @row: Active rows */ struct sdw_bus_params { enum sdw_reg_bank curr_bank; enum sdw_reg_bank next_bank; + unsigned int max_dr_freq; + unsigned int curr_dr_freq; + unsigned int bandwidth; + unsigned int col; + unsigned int row; }; /** @@ -426,6 +461,7 @@ struct sdw_bus_params { * @interrupt_callback: Device interrupt notification (invoked in thread * context) * @update_status: Update Slave status + * @bus_config: Update the bus config for Slave * @port_prep: Prepare the port with parameters */ struct sdw_slave_ops { @@ -434,6 +470,8 @@ struct sdw_slave_ops { struct sdw_slave_intr_status *status); int (*update_status)(struct sdw_slave *slave, enum sdw_slave_status status); + int (*bus_config)(struct sdw_slave *slave, + struct sdw_bus_params *params); int (*port_prep)(struct sdw_slave *slave, struct sdw_prepare_ch *prepare_ch, enum sdw_port_prep_ops pre_ops); @@ -597,6 +635,9 @@ struct sdw_defer { * @xfer_msg: Transfer message callback * @xfer_msg_defer: Defer version of transfer message callback * @reset_page_addr: Reset the SCP page address registers + * @set_bus_conf: Set the bus configuration + * @pre_bank_switch: Callback for pre bank switch + * @post_bank_switch: Callback for post bank switch */ struct sdw_master_ops { int (*read_prop)(struct sdw_bus *bus); @@ -608,6 +649,11 @@ struct sdw_master_ops { struct sdw_defer *defer); enum sdw_command_response (*reset_page_addr) (struct sdw_bus *bus, unsigned int dev_num); + int (*set_bus_conf)(struct sdw_bus *bus, + struct sdw_bus_params *params); + int (*pre_bank_switch)(struct sdw_bus *bus); + int (*post_bank_switch)(struct sdw_bus *bus); + }; /** @@ -628,6 +674,7 @@ struct sdw_master_ops { * transport and port parameters * @defer_msg: Defer message * @clk_stop_timeout: Clock stop timeout computed + * @bank_switch_timeout: Bank switch timeout computed */ struct sdw_bus { struct device *dev; @@ -643,6 +690,7 @@ struct sdw_bus { struct list_head m_rt_list; struct sdw_defer defer_msg; unsigned int clk_stop_timeout; + u32 bank_switch_timeout; }; int sdw_add_bus_master(struct sdw_bus *bus); -- cgit v1.2.3