| /* Copyright (c) 2012, Code Aurora Forum. All rights reserved. |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License version 2 and |
| * only version 2 as published by the Free Software Foundation. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| */ |
| #include <linux/slab.h> |
| #include <linux/mutex.h> |
| #include <linux/mfd/wcd9xxx/wcd9xxx-slimslave.h> |
| |
| struct wcd9xxx_slim_sch_rx { |
| u32 sph; |
| u32 ch_num; |
| u16 ch_h; |
| u16 grph; |
| }; |
| |
| struct wcd9xxx_slim_sch_tx { |
| u32 sph; |
| u32 ch_num; |
| u16 ch_h; |
| u16 grph; |
| }; |
| |
| struct wcd9xxx_slim_sch { |
| struct wcd9xxx_slim_sch_rx rx[SLIM_MAX_RX_PORTS]; |
| struct wcd9xxx_slim_sch_tx tx[SLIM_MAX_TX_PORTS]; |
| }; |
| |
| static struct wcd9xxx_slim_sch sh_ch; |
| |
| static int wcd9xxx_alloc_slim_sh_ch_rx(struct wcd9xxx *wcd9xxx, |
| u8 wcd9xxx_pgd_la); |
| static int wcd9xxx_alloc_slim_sh_ch_tx(struct wcd9xxx *wcd9xxx, |
| u8 wcd9xxx_pgd_la); |
| static int wcd9xxx_dealloc_slim_sh_ch_rx(struct wcd9xxx *wcd9xxx); |
| static int wcd9xxx_dealloc_slim_sh_ch_tx(struct wcd9xxx *wcd9xxx); |
| |
| int wcd9xxx_init_slimslave(struct wcd9xxx *wcd9xxx, u8 wcd9xxx_pgd_la) |
| { |
| int ret = 0; |
| |
| ret = wcd9xxx_alloc_slim_sh_ch_rx(wcd9xxx, wcd9xxx_pgd_la); |
| if (ret) { |
| pr_err("%s: Failed to alloc rx slimbus shared channels\n", |
| __func__); |
| goto rx_err; |
| } |
| ret = wcd9xxx_alloc_slim_sh_ch_tx(wcd9xxx, wcd9xxx_pgd_la); |
| if (ret) { |
| pr_err("%s: Failed to alloc tx slimbus shared channels\n", |
| __func__); |
| goto tx_err; |
| } |
| return 0; |
| tx_err: |
| wcd9xxx_dealloc_slim_sh_ch_rx(wcd9xxx); |
| rx_err: |
| return ret; |
| } |
| |
| |
| int wcd9xxx_deinit_slimslave(struct wcd9xxx *wcd9xxx) |
| { |
| int ret = 0; |
| ret = wcd9xxx_dealloc_slim_sh_ch_rx(wcd9xxx); |
| if (ret < 0) { |
| pr_err("%s: fail to dealloc rx slim ports\n", __func__); |
| goto err; |
| } |
| ret = wcd9xxx_dealloc_slim_sh_ch_tx(wcd9xxx); |
| if (ret < 0) { |
| pr_err("%s: fail to dealloc tx slim ports\n", __func__); |
| goto err; |
| } |
| err: |
| return ret; |
| } |
| |
| int wcd9xxx_get_channel(struct wcd9xxx *wcd9xxx, |
| unsigned int *rx_ch, |
| unsigned int *tx_ch) |
| { |
| int ch_idx = 0; |
| struct wcd9xxx_slim_sch_rx *rx = sh_ch.rx; |
| struct wcd9xxx_slim_sch_tx *tx = sh_ch.tx; |
| |
| for (ch_idx = 0; ch_idx < SLIM_MAX_RX_PORTS; ch_idx++) |
| rx_ch[ch_idx] = rx[ch_idx].ch_num; |
| for (ch_idx = 0; ch_idx < SLIM_MAX_TX_PORTS; ch_idx++) |
| tx_ch[ch_idx] = tx[ch_idx].ch_num; |
| return 0; |
| } |
| |
| static int wcd9xxx_alloc_slim_sh_ch_rx(struct wcd9xxx *wcd9xxx, |
| u8 wcd9xxx_pgd_la) |
| { |
| int ret = 0; |
| u8 ch_idx ; |
| u16 slave_port_id = 0; |
| struct wcd9xxx_slim_sch_rx *rx = sh_ch.rx; |
| |
| /* |
| * DSP requires channel number to be between 128 and 255. |
| */ |
| pr_debug("%s: pgd_la[%d]\n", __func__, wcd9xxx_pgd_la); |
| for (ch_idx = 0; ch_idx < SLIM_MAX_RX_PORTS; ch_idx++) { |
| slave_port_id = (ch_idx + 1 + |
| SB_PGD_OFFSET_OF_RX_SLAVE_DEV_PORTS); |
| rx[ch_idx].ch_num = slave_port_id + BASE_CH_NUM; |
| ret = slim_get_slaveport(wcd9xxx_pgd_la, slave_port_id, |
| &rx[ch_idx].sph, SLIM_SINK); |
| if (ret < 0) { |
| pr_err("%s: slave port failure id[%d] ret[%d]\n", |
| __func__, slave_port_id, ret); |
| goto err; |
| } |
| |
| ret = slim_query_ch(wcd9xxx->slim, rx[ch_idx].ch_num, |
| &rx[ch_idx].ch_h); |
| if (ret < 0) { |
| pr_err("%s: slim_query_ch failed ch-num[%d] ret[%d]\n", |
| __func__, rx[ch_idx].ch_num, ret); |
| goto err; |
| } |
| } |
| err: |
| return ret; |
| } |
| |
| static int wcd9xxx_alloc_slim_sh_ch_tx(struct wcd9xxx *wcd9xxx, |
| u8 wcd9xxx_pgd_la) |
| { |
| int ret = 0; |
| u8 ch_idx ; |
| struct wcd9xxx_slim_sch_tx *tx = sh_ch.tx; |
| u16 slave_port_id = 0; |
| |
| pr_debug("%s: pgd_la[%d]\n", __func__, wcd9xxx_pgd_la); |
| /* DSP requires channel number to be between 128 and 255. For RX port |
| * use channel numbers from 138 to 144, for TX port |
| * use channel numbers from 128 to 137 |
| */ |
| for (ch_idx = 0; ch_idx < SLIM_MAX_TX_PORTS; ch_idx++) { |
| slave_port_id = ch_idx; |
| tx[ch_idx].ch_num = slave_port_id + BASE_CH_NUM; |
| ret = slim_get_slaveport(wcd9xxx_pgd_la, slave_port_id, |
| &tx[ch_idx].sph, SLIM_SRC); |
| if (ret < 0) { |
| pr_err("%s: slave port failure id[%d] ret[%d]\n", |
| __func__, slave_port_id, ret); |
| goto err; |
| } |
| ret = slim_query_ch(wcd9xxx->slim, tx[ch_idx].ch_num, |
| &tx[ch_idx].ch_h); |
| if (ret < 0) { |
| pr_err("%s: slim_query_ch failed ch-num[%d] ret[%d]\n", |
| __func__, tx[ch_idx].ch_num, ret); |
| goto err; |
| } |
| } |
| err: |
| return ret; |
| } |
| |
| static int wcd9xxx_dealloc_slim_sh_ch_rx(struct wcd9xxx *wcd9xxx) |
| { |
| int idx = 0; |
| int ret = 0; |
| struct wcd9xxx_slim_sch_rx *rx = sh_ch.rx; |
| /* slim_dealloc_ch */ |
| for (idx = 0; idx < SLIM_MAX_RX_PORTS; idx++) { |
| ret = slim_dealloc_ch(wcd9xxx->slim, rx[idx].ch_h); |
| if (ret < 0) { |
| pr_err("%s: slim_dealloc_ch fail ret[%d] ch_h[%d]\n", |
| __func__, ret, rx[idx].ch_h); |
| } |
| } |
| memset(sh_ch.rx, 0, sizeof(sh_ch.rx)); |
| return ret; |
| } |
| |
| static int wcd9xxx_dealloc_slim_sh_ch_tx(struct wcd9xxx *wcd9xxx) |
| { |
| int idx = 0; |
| int ret = 0; |
| struct wcd9xxx_slim_sch_tx *tx = sh_ch.tx; |
| /* slim_dealloc_ch */ |
| for (idx = 0; idx < SLIM_MAX_TX_PORTS; idx++) { |
| ret = slim_dealloc_ch(wcd9xxx->slim, tx[idx].ch_h); |
| if (ret < 0) { |
| pr_err("%s: slim_dealloc_ch fail ret[%d] ch_h[%d]\n", |
| __func__, ret, tx[idx].ch_h); |
| } |
| } |
| memset(sh_ch.tx, 0, sizeof(sh_ch.tx)); |
| return ret; |
| } |
| |
| /* Enable slimbus slave device for RX path */ |
| int wcd9xxx_cfg_slim_sch_rx(struct wcd9xxx *wcd9xxx, unsigned int *ch_num, |
| unsigned int ch_cnt, unsigned int rate) |
| { |
| u8 i = 0; |
| u16 grph; |
| u32 sph[SLIM_MAX_RX_PORTS] = {0}; |
| u16 ch_h[SLIM_MAX_RX_PORTS] = {0}; |
| u16 slave_port_id; |
| u8 payload_rx = 0, wm_payload = 0; |
| int ret, idx = 0; |
| unsigned short multi_chan_cfg_reg_addr; |
| struct wcd9xxx_slim_sch_rx *rx = sh_ch.rx; |
| struct slim_ch prop; |
| |
| /* Configure slave interface device */ |
| pr_debug("%s: ch_cnt[%d] rate=%d\n", __func__, ch_cnt, rate); |
| |
| for (i = 0; i < ch_cnt; i++) { |
| idx = (ch_num[i] - BASE_CH_NUM - |
| SB_PGD_OFFSET_OF_RX_SLAVE_DEV_PORTS - 1); |
| ch_h[i] = rx[idx].ch_h; |
| sph[i] = rx[idx].sph; |
| slave_port_id = idx + 1; |
| if ((slave_port_id > SB_PGD_MAX_NUMBER_OF_RX_SLAVE_DEV_PORTS) || |
| (slave_port_id == 0)) { |
| pr_err("Slimbus: invalid slave port id: %d", |
| slave_port_id); |
| ret = -EINVAL; |
| goto err; |
| } |
| slave_port_id += SB_PGD_OFFSET_OF_RX_SLAVE_DEV_PORTS; |
| /* look for the valid port range and chose the |
| * payload accordingly |
| */ |
| if ((slave_port_id > |
| SB_PGD_TX_PORT_MULTI_CHANNEL_1_END_PORT_ID) && |
| (slave_port_id <= |
| SB_PGD_RX_PORT_MULTI_CHANNEL_0_END_PORT_ID)) { |
| payload_rx = payload_rx | |
| (1 << |
| (slave_port_id - |
| SB_PGD_RX_PORT_MULTI_CHANNEL_0_START_PORT_ID)); |
| } else { |
| ret = -EINVAL; |
| goto err; |
| } |
| multi_chan_cfg_reg_addr = |
| SB_PGD_RX_PORT_MULTI_CHANNEL_0(slave_port_id); |
| /* write to interface device */ |
| ret = wcd9xxx_interface_reg_write(wcd9xxx, |
| multi_chan_cfg_reg_addr, |
| payload_rx); |
| if (ret < 0) { |
| pr_err("%s:Intf-dev fail reg[%d] payload[%d] ret[%d]\n", |
| __func__, |
| multi_chan_cfg_reg_addr, |
| payload_rx, ret); |
| goto err; |
| } |
| /* configure the slave port for water mark and enable*/ |
| wm_payload = (SLAVE_PORT_WATER_MARK_VALUE << |
| SLAVE_PORT_WATER_MARK_SHIFT) + |
| SLAVE_PORT_ENABLE; |
| ret = wcd9xxx_interface_reg_write(wcd9xxx, |
| SB_PGD_PORT_CFG_BYTE_ADDR(slave_port_id), |
| wm_payload); |
| if (ret < 0) { |
| pr_err("%s:watermark set failure for port[%d] ret[%d]", |
| __func__, slave_port_id, ret); |
| } |
| } |
| |
| /* slim_define_ch api */ |
| prop.prot = SLIM_AUTO_ISO; |
| prop.baser = SLIM_RATE_4000HZ; |
| prop.dataf = SLIM_CH_DATAF_NOT_DEFINED; |
| prop.auxf = SLIM_CH_AUXF_NOT_APPLICABLE; |
| prop.ratem = (rate/4000); |
| prop.sampleszbits = 16; |
| |
| ret = slim_define_ch(wcd9xxx->slim, &prop, ch_h, ch_cnt, |
| true, &grph); |
| if (ret < 0) { |
| pr_err("%s: slim_define_ch failed ret[%d]\n", |
| __func__, ret); |
| goto err; |
| } |
| for (i = 0; i < ch_cnt; i++) { |
| ret = slim_connect_sink(wcd9xxx->slim, &sph[i], |
| 1, ch_h[i]); |
| if (ret < 0) { |
| pr_err("%s: slim_connect_sink failed ret[%d]\n", |
| __func__, ret); |
| goto err_close_slim_sch; |
| } |
| } |
| /* slim_control_ch */ |
| ret = slim_control_ch(wcd9xxx->slim, grph, SLIM_CH_ACTIVATE, |
| true); |
| if (ret < 0) { |
| pr_err("%s: slim_control_ch failed ret[%d]\n", |
| __func__, ret); |
| goto err_close_slim_sch; |
| } |
| for (i = 0; i < ch_cnt; i++) { |
| idx = (ch_num[i] - BASE_CH_NUM - |
| SB_PGD_OFFSET_OF_RX_SLAVE_DEV_PORTS - 1); |
| rx[idx].grph = grph; |
| } |
| return 0; |
| |
| err_close_slim_sch: |
| /* release all acquired handles */ |
| wcd9xxx_close_slim_sch_rx(wcd9xxx, ch_num, ch_cnt); |
| err: |
| return ret; |
| } |
| EXPORT_SYMBOL_GPL(wcd9xxx_cfg_slim_sch_rx); |
| |
| /* Enable slimbus slave device for RX path */ |
| int wcd9xxx_cfg_slim_sch_tx(struct wcd9xxx *wcd9xxx, unsigned int *ch_num, |
| unsigned int ch_cnt, unsigned int rate) |
| { |
| u8 i = 0; |
| u8 payload_tx_0 = 0, payload_tx_1 = 0, wm_payload = 0; |
| u16 grph; |
| u32 sph[SLIM_MAX_TX_PORTS] = {0}; |
| u16 ch_h[SLIM_MAX_TX_PORTS] = {0}; |
| u16 idx = 0, slave_port_id; |
| int ret = 0; |
| unsigned short multi_chan_cfg_reg_addr; |
| |
| struct wcd9xxx_slim_sch_tx *tx = sh_ch.tx; |
| struct slim_ch prop; |
| |
| pr_debug("%s: ch_cnt[%d] rate[%d]\n", __func__, ch_cnt, rate); |
| for (i = 0; i < ch_cnt; i++) { |
| idx = (ch_num[i] - BASE_CH_NUM); |
| ch_h[i] = tx[idx].ch_h; |
| sph[i] = tx[idx].sph; |
| slave_port_id = idx ; |
| if (slave_port_id > SB_PGD_MAX_NUMBER_OF_TX_SLAVE_DEV_PORTS) { |
| pr_err("SLIMbus: invalid slave port id: %d", |
| slave_port_id); |
| ret = -EINVAL; |
| goto err; |
| } |
| /* look for the valid port range and chose the |
| * payload accordingly |
| */ |
| if (slave_port_id <= |
| SB_PGD_TX_PORT_MULTI_CHANNEL_0_END_PORT_ID) { |
| payload_tx_0 = payload_tx_0 | (1 << slave_port_id); |
| } else if (slave_port_id <= |
| SB_PGD_TX_PORT_MULTI_CHANNEL_1_END_PORT_ID) { |
| payload_tx_1 = payload_tx_1 | |
| (1 << |
| (slave_port_id - |
| SB_PGD_TX_PORT_MULTI_CHANNEL_1_START_PORT_ID)); |
| } else { |
| ret = -EINVAL; |
| goto err; |
| } |
| multi_chan_cfg_reg_addr = |
| SB_PGD_TX_PORT_MULTI_CHANNEL_0(slave_port_id); |
| /* write to interface device */ |
| ret = wcd9xxx_interface_reg_write(wcd9xxx, |
| multi_chan_cfg_reg_addr, |
| payload_tx_0); |
| if (ret < 0) { |
| pr_err("%s:Intf-dev fail reg[%d] payload[%d] ret[%d]\n", |
| __func__, |
| multi_chan_cfg_reg_addr, |
| payload_tx_0, ret); |
| goto err; |
| } |
| multi_chan_cfg_reg_addr = |
| SB_PGD_TX_PORT_MULTI_CHANNEL_1(slave_port_id); |
| /* ports 8,9 */ |
| ret = wcd9xxx_interface_reg_write(wcd9xxx, |
| multi_chan_cfg_reg_addr, |
| payload_tx_1); |
| if (ret < 0) { |
| pr_err("%s:Intf-dev fail reg[%d] payload[%d] ret[%d]\n", |
| __func__, |
| multi_chan_cfg_reg_addr, |
| payload_tx_1, ret); |
| goto err; |
| } |
| /* configure the slave port for water mark and enable*/ |
| wm_payload = (SLAVE_PORT_WATER_MARK_VALUE << |
| SLAVE_PORT_WATER_MARK_SHIFT) + |
| SLAVE_PORT_ENABLE; |
| ret = wcd9xxx_interface_reg_write(wcd9xxx, |
| SB_PGD_PORT_CFG_BYTE_ADDR(slave_port_id), |
| wm_payload); |
| if (ret < 0) { |
| pr_err("%s:watermark set failure for port[%d] ret[%d]", |
| __func__, |
| slave_port_id, ret); |
| } |
| } |
| |
| /* slim_define_ch api */ |
| prop.prot = SLIM_AUTO_ISO; |
| prop.baser = SLIM_RATE_4000HZ; |
| prop.dataf = SLIM_CH_DATAF_NOT_DEFINED; |
| prop.auxf = SLIM_CH_AUXF_NOT_APPLICABLE; |
| prop.ratem = (rate/4000); |
| prop.sampleszbits = 16; |
| ret = slim_define_ch(wcd9xxx->slim, &prop, ch_h, ch_cnt, |
| true, &grph); |
| if (ret < 0) { |
| pr_err("%s: slim_define_ch failed ret[%d]\n", |
| __func__, ret); |
| goto err; |
| } |
| for (i = 0; i < ch_cnt; i++) { |
| ret = slim_connect_src(wcd9xxx->slim, sph[i], |
| ch_h[i]); |
| if (ret < 0) { |
| pr_err("%s: slim_connect_src failed ret[%d]\n", |
| __func__, ret); |
| goto err; |
| } |
| } |
| /* slim_control_ch */ |
| ret = slim_control_ch(wcd9xxx->slim, grph, SLIM_CH_ACTIVATE, |
| true); |
| if (ret < 0) { |
| pr_err("%s: slim_control_ch failed ret[%d]\n", |
| __func__, ret); |
| goto err; |
| } |
| for (i = 0; i < ch_cnt; i++) { |
| idx = (ch_num[i] - BASE_CH_NUM); |
| tx[idx].grph = grph; |
| } |
| return 0; |
| err: |
| /* release all acquired handles */ |
| wcd9xxx_close_slim_sch_tx(wcd9xxx, ch_num, ch_cnt); |
| return ret; |
| } |
| EXPORT_SYMBOL_GPL(wcd9xxx_cfg_slim_sch_tx); |
| |
| int wcd9xxx_close_slim_sch_rx(struct wcd9xxx *wcd9xxx, unsigned int *ch_num, |
| unsigned int ch_cnt) |
| { |
| u16 grph = 0; |
| u32 sph[SLIM_MAX_RX_PORTS] = {0}; |
| int i = 0 , idx = 0; |
| int ret = 0; |
| struct wcd9xxx_slim_sch_rx *rx = sh_ch.rx; |
| |
| pr_debug("%s: ch_cnt[%d]\n", __func__, ch_cnt); |
| for (i = 0; i < ch_cnt; i++) { |
| idx = (ch_num[i] - BASE_CH_NUM - |
| SB_PGD_OFFSET_OF_RX_SLAVE_DEV_PORTS - 1); |
| sph[i] = rx[idx].sph; |
| grph = rx[idx].grph; |
| } |
| |
| /* slim_disconnect_port */ |
| ret = slim_disconnect_ports(wcd9xxx->slim, sph, ch_cnt); |
| if (ret < 0) { |
| pr_err("%s: slim_disconnect_ports failed ret[%d]\n", |
| __func__, ret); |
| } |
| /* slim_control_ch (REMOVE) */ |
| ret = slim_control_ch(wcd9xxx->slim, grph, SLIM_CH_REMOVE, true); |
| if (ret < 0) { |
| pr_err("%s: slim_control_ch failed ret[%d]\n", |
| __func__, ret); |
| goto err; |
| } |
| for (i = 0; i < ch_cnt; i++) { |
| idx = (ch_num[i] - BASE_CH_NUM - |
| SB_PGD_OFFSET_OF_RX_SLAVE_DEV_PORTS - 1); |
| rx[idx].grph = 0; |
| } |
| err: |
| return ret; |
| } |
| EXPORT_SYMBOL_GPL(wcd9xxx_close_slim_sch_rx); |
| |
| int wcd9xxx_close_slim_sch_tx(struct wcd9xxx *wcd9xxx, unsigned int *ch_num, |
| unsigned int ch_cnt) |
| { |
| u16 grph = 0; |
| u32 sph[SLIM_MAX_TX_PORTS] = {0}; |
| int ret = 0; |
| int i = 0 , idx = 0; |
| struct wcd9xxx_slim_sch_tx *tx = sh_ch.tx; |
| |
| pr_debug("%s: ch_cnt[%d]\n", __func__, ch_cnt); |
| for (i = 0; i < ch_cnt; i++) { |
| idx = (ch_num[i] - BASE_CH_NUM); |
| sph[i] = tx[idx].sph; |
| grph = tx[idx].grph; |
| } |
| /* slim_disconnect_port */ |
| ret = slim_disconnect_ports(wcd9xxx->slim, sph, ch_cnt); |
| if (ret < 0) { |
| pr_err("%s: slim_disconnect_ports failed ret[%d]\n", |
| __func__, ret); |
| } |
| /* slim_control_ch (REMOVE) */ |
| ret = slim_control_ch(wcd9xxx->slim, grph, SLIM_CH_REMOVE, true); |
| if (ret < 0) { |
| pr_err("%s: slim_control_ch failed ret[%d]\n", |
| __func__, ret); |
| goto err; |
| } |
| for (i = 0; i < ch_cnt; i++) { |
| idx = (ch_num[i] - BASE_CH_NUM); |
| tx[idx].grph = 0; |
| } |
| err: |
| return ret; |
| } |
| EXPORT_SYMBOL_GPL(wcd9xxx_close_slim_sch_tx); |