| // SPDX-License-Identifier: GPL-2.0 |
| // |
| // Mediatek ALSA BT SCO CVSD/MSBC Driver |
| // |
| // Copyright (c) 2019 MediaTek Inc. |
| // Author: KaiChieh Chuang <kaichieh.chuang@mediatek.com> |
| |
| #include <linux/mfd/syscon.h> |
| #include <linux/module.h> |
| #include <linux/of_address.h> |
| #include <linux/sched/clock.h> |
| |
| #include <sound/soc.h> |
| |
| #define BTCVSD_SND_NAME "mtk-btcvsd-snd" |
| |
| #define BT_CVSD_TX_NREADY BIT(21) |
| #define BT_CVSD_RX_READY BIT(22) |
| #define BT_CVSD_TX_UNDERFLOW BIT(23) |
| #define BT_CVSD_RX_OVERFLOW BIT(24) |
| #define BT_CVSD_INTERRUPT BIT(31) |
| |
| #define BT_CVSD_CLEAR \ |
| (BT_CVSD_TX_NREADY | BT_CVSD_RX_READY | BT_CVSD_TX_UNDERFLOW |\ |
| BT_CVSD_RX_OVERFLOW | BT_CVSD_INTERRUPT) |
| |
| /* TX */ |
| #define SCO_TX_ENCODE_SIZE (60) |
| /* 18 = 6 * 180 / SCO_TX_ENCODE_SIZE */ |
| #define SCO_TX_PACKER_BUF_NUM (18) |
| |
| /* RX */ |
| #define SCO_RX_PLC_SIZE (30) |
| #define SCO_RX_PACKER_BUF_NUM (64) |
| #define SCO_RX_PACKET_MASK (0x3F) |
| |
| #define SCO_CVSD_PACKET_VALID_SIZE 2 |
| |
| #define SCO_PACKET_120 120 |
| #define SCO_PACKET_180 180 |
| |
| #define BTCVSD_RX_PACKET_SIZE (SCO_RX_PLC_SIZE + SCO_CVSD_PACKET_VALID_SIZE) |
| #define BTCVSD_TX_PACKET_SIZE (SCO_TX_ENCODE_SIZE) |
| |
| #define BTCVSD_RX_BUF_SIZE (BTCVSD_RX_PACKET_SIZE * SCO_RX_PACKER_BUF_NUM) |
| #define BTCVSD_TX_BUF_SIZE (BTCVSD_TX_PACKET_SIZE * SCO_TX_PACKER_BUF_NUM) |
| |
| enum bt_sco_state { |
| BT_SCO_STATE_IDLE, |
| BT_SCO_STATE_RUNNING, |
| BT_SCO_STATE_ENDING, |
| }; |
| |
| enum bt_sco_direct { |
| BT_SCO_DIRECT_BT2ARM, |
| BT_SCO_DIRECT_ARM2BT, |
| }; |
| |
| enum bt_sco_packet_len { |
| BT_SCO_CVSD_30 = 0, |
| BT_SCO_CVSD_60, |
| BT_SCO_CVSD_90, |
| BT_SCO_CVSD_120, |
| BT_SCO_CVSD_10, |
| BT_SCO_CVSD_20, |
| BT_SCO_CVSD_MAX, |
| }; |
| |
| enum BT_SCO_BAND { |
| BT_SCO_NB, |
| BT_SCO_WB, |
| }; |
| |
| struct mtk_btcvsd_snd_hw_info { |
| unsigned int num_valid_addr; |
| unsigned long bt_sram_addr[20]; |
| unsigned int packet_length; |
| unsigned int packet_num; |
| }; |
| |
| struct mtk_btcvsd_snd_stream { |
| struct snd_pcm_substream *substream; |
| int stream; |
| |
| enum bt_sco_state state; |
| |
| unsigned int packet_size; |
| unsigned int buf_size; |
| u8 temp_packet_buf[SCO_PACKET_180]; |
| |
| int packet_w; |
| int packet_r; |
| snd_pcm_uframes_t prev_frame; |
| int prev_packet_idx; |
| |
| unsigned int xrun:1; |
| unsigned int timeout:1; |
| unsigned int mute:1; |
| unsigned int trigger_start:1; |
| unsigned int wait_flag:1; |
| unsigned int rw_cnt; |
| |
| unsigned long long time_stamp; |
| unsigned long long buf_data_equivalent_time; |
| |
| struct mtk_btcvsd_snd_hw_info buffer_info; |
| }; |
| |
| struct mtk_btcvsd_snd { |
| struct device *dev; |
| int irq_id; |
| |
| struct regmap *infra; |
| void __iomem *bt_pkv_base; |
| void __iomem *bt_sram_bank2_base; |
| |
| unsigned int infra_misc_offset; |
| unsigned int conn_bt_cvsd_mask; |
| unsigned int cvsd_mcu_read_offset; |
| unsigned int cvsd_mcu_write_offset; |
| unsigned int cvsd_packet_indicator; |
| |
| u32 *bt_reg_pkt_r; |
| u32 *bt_reg_pkt_w; |
| u32 *bt_reg_ctl; |
| |
| unsigned int irq_disabled:1; |
| |
| spinlock_t tx_lock; /* spinlock for bt tx stream control */ |
| spinlock_t rx_lock; /* spinlock for bt rx stream control */ |
| wait_queue_head_t tx_wait; |
| wait_queue_head_t rx_wait; |
| |
| struct mtk_btcvsd_snd_stream *tx; |
| struct mtk_btcvsd_snd_stream *rx; |
| u8 tx_packet_buf[BTCVSD_TX_BUF_SIZE]; |
| u8 rx_packet_buf[BTCVSD_RX_BUF_SIZE]; |
| |
| enum BT_SCO_BAND band; |
| }; |
| |
| struct mtk_btcvsd_snd_time_buffer_info { |
| unsigned long long data_count_equi_time; |
| unsigned long long time_stamp_us; |
| }; |
| |
| static const unsigned int btsco_packet_valid_mask[BT_SCO_CVSD_MAX][6] = { |
| {0x1, 0x1 << 1, 0x1 << 2, 0x1 << 3, 0x1 << 4, 0x1 << 5}, |
| {0x1, 0x1, 0x2, 0x2, 0x4, 0x4}, |
| {0x1, 0x1, 0x1, 0x2, 0x2, 0x2}, |
| {0x1, 0x1, 0x1, 0x1, 0x0, 0x0}, |
| {0x7, 0x7 << 3, 0x7 << 6, 0x7 << 9, 0x7 << 12, 0x7 << 15}, |
| {0x3, 0x3 << 1, 0x3 << 3, 0x3 << 4, 0x3 << 6, 0x3 << 7}, |
| }; |
| |
| static const unsigned int btsco_packet_info[BT_SCO_CVSD_MAX][4] = { |
| {30, 6, SCO_PACKET_180 / SCO_TX_ENCODE_SIZE, |
| SCO_PACKET_180 / SCO_RX_PLC_SIZE}, |
| {60, 3, SCO_PACKET_180 / SCO_TX_ENCODE_SIZE, |
| SCO_PACKET_180 / SCO_RX_PLC_SIZE}, |
| {90, 2, SCO_PACKET_180 / SCO_TX_ENCODE_SIZE, |
| SCO_PACKET_180 / SCO_RX_PLC_SIZE}, |
| {120, 1, SCO_PACKET_120 / SCO_TX_ENCODE_SIZE, |
| SCO_PACKET_120 / SCO_RX_PLC_SIZE}, |
| {10, 18, SCO_PACKET_180 / SCO_TX_ENCODE_SIZE, |
| SCO_PACKET_180 / SCO_RX_PLC_SIZE}, |
| {20, 9, SCO_PACKET_180 / SCO_TX_ENCODE_SIZE, |
| SCO_PACKET_180 / SCO_RX_PLC_SIZE}, |
| }; |
| |
| static const u8 table_msbc_silence[SCO_PACKET_180] = { |
| 0x01, 0x38, 0xad, 0x00, 0x00, 0xc5, 0x00, 0x00, 0x00, 0x00, |
| 0x77, 0x6d, 0xb6, 0xdd, 0xdb, 0x6d, 0xb7, 0x76, 0xdb, 0x6d, |
| 0xdd, 0xb6, 0xdb, 0x77, 0x6d, 0xb6, 0xdd, 0xdb, 0x6d, 0xb7, |
| 0x76, 0xdb, 0x6d, 0xdd, 0xb6, 0xdb, 0x77, 0x6d, 0xb6, 0xdd, |
| 0xdb, 0x6d, 0xb7, 0x76, 0xdb, 0x6d, 0xdd, 0xb6, 0xdb, 0x77, |
| 0x6d, 0xb6, 0xdd, 0xdb, 0x6d, 0xb7, 0x76, 0xdb, 0x6c, 0x00, |
| 0x01, 0xc8, 0xad, 0x00, 0x00, 0xc5, 0x00, 0x00, 0x00, 0x00, |
| 0x77, 0x6d, 0xb6, 0xdd, 0xdb, 0x6d, 0xb7, 0x76, 0xdb, 0x6d, |
| 0xdd, 0xb6, 0xdb, 0x77, 0x6d, 0xb6, 0xdd, 0xdb, 0x6d, 0xb7, |
| 0x76, 0xdb, 0x6d, 0xdd, 0xb6, 0xdb, 0x77, 0x6d, 0xb6, 0xdd, |
| 0xdb, 0x6d, 0xb7, 0x76, 0xdb, 0x6d, 0xdd, 0xb6, 0xdb, 0x77, |
| 0x6d, 0xb6, 0xdd, 0xdb, 0x6d, 0xb7, 0x76, 0xdb, 0x6c, 0x00, |
| 0x01, 0xf8, 0xad, 0x00, 0x00, 0xc5, 0x00, 0x00, 0x00, 0x00, |
| 0x77, 0x6d, 0xb6, 0xdd, 0xdb, 0x6d, 0xb7, 0x76, 0xdb, 0x6d, |
| 0xdd, 0xb6, 0xdb, 0x77, 0x6d, 0xb6, 0xdd, 0xdb, 0x6d, 0xb7, |
| 0x76, 0xdb, 0x6d, 0xdd, 0xb6, 0xdb, 0x77, 0x6d, 0xb6, 0xdd, |
| 0xdb, 0x6d, 0xb7, 0x76, 0xdb, 0x6d, 0xdd, 0xb6, 0xdb, 0x77, |
| 0x6d, 0xb6, 0xdd, 0xdb, 0x6d, 0xb7, 0x76, 0xdb, 0x6c, 0x00 |
| }; |
| |
| static void mtk_btcvsd_snd_irq_enable(struct mtk_btcvsd_snd *bt) |
| { |
| regmap_update_bits(bt->infra, bt->infra_misc_offset, |
| bt->conn_bt_cvsd_mask, bt->conn_bt_cvsd_mask); |
| } |
| |
| static void mtk_btcvsd_snd_irq_disable(struct mtk_btcvsd_snd *bt) |
| { |
| regmap_update_bits(bt->infra, bt->infra_misc_offset, |
| bt->conn_bt_cvsd_mask, 0); |
| } |
| |
| static void mtk_btcvsd_snd_set_state(struct mtk_btcvsd_snd *bt, |
| struct mtk_btcvsd_snd_stream *bt_stream, |
| int state) |
| { |
| dev_dbg(bt->dev, "%s(), stream %d, state %d, tx->state %d, rx->state %d, irq_disabled %d\n", |
| __func__, |
| bt_stream->stream, state, |
| bt->tx->state, bt->rx->state, bt->irq_disabled); |
| |
| bt_stream->state = state; |
| |
| if (bt->tx->state == BT_SCO_STATE_IDLE && |
| bt->rx->state == BT_SCO_STATE_IDLE) { |
| if (!bt->irq_disabled) { |
| disable_irq(bt->irq_id); |
| mtk_btcvsd_snd_irq_disable(bt); |
| bt->irq_disabled = 1; |
| } |
| } else { |
| if (bt->irq_disabled) { |
| enable_irq(bt->irq_id); |
| mtk_btcvsd_snd_irq_enable(bt); |
| bt->irq_disabled = 0; |
| } |
| } |
| } |
| |
| static int mtk_btcvsd_snd_tx_init(struct mtk_btcvsd_snd *bt) |
| { |
| memset(bt->tx, 0, sizeof(*bt->tx)); |
| memset(bt->tx_packet_buf, 0, sizeof(bt->tx_packet_buf)); |
| |
| bt->tx->packet_size = BTCVSD_TX_PACKET_SIZE; |
| bt->tx->buf_size = BTCVSD_TX_BUF_SIZE; |
| bt->tx->timeout = 0; |
| bt->tx->rw_cnt = 0; |
| bt->tx->stream = SNDRV_PCM_STREAM_PLAYBACK; |
| return 0; |
| } |
| |
| static int mtk_btcvsd_snd_rx_init(struct mtk_btcvsd_snd *bt) |
| { |
| memset(bt->rx, 0, sizeof(*bt->rx)); |
| memset(bt->rx_packet_buf, 0, sizeof(bt->rx_packet_buf)); |
| |
| bt->rx->packet_size = BTCVSD_RX_PACKET_SIZE; |
| bt->rx->buf_size = BTCVSD_RX_BUF_SIZE; |
| bt->rx->timeout = 0; |
| bt->rx->rw_cnt = 0; |
| bt->tx->stream = SNDRV_PCM_STREAM_CAPTURE; |
| return 0; |
| } |
| |
| static void get_tx_time_stamp(struct mtk_btcvsd_snd *bt, |
| struct mtk_btcvsd_snd_time_buffer_info *ts) |
| { |
| ts->time_stamp_us = bt->tx->time_stamp; |
| ts->data_count_equi_time = bt->tx->buf_data_equivalent_time; |
| } |
| |
| static void get_rx_time_stamp(struct mtk_btcvsd_snd *bt, |
| struct mtk_btcvsd_snd_time_buffer_info *ts) |
| { |
| ts->time_stamp_us = bt->rx->time_stamp; |
| ts->data_count_equi_time = bt->rx->buf_data_equivalent_time; |
| } |
| |
| static int btcvsd_bytes_to_frame(struct snd_pcm_substream *substream, |
| int bytes) |
| { |
| int count = bytes; |
| struct snd_pcm_runtime *runtime = substream->runtime; |
| |
| if (runtime->format == SNDRV_PCM_FORMAT_S32_LE || |
| runtime->format == SNDRV_PCM_FORMAT_U32_LE) |
| count = count >> 2; |
| else |
| count = count >> 1; |
| |
| count = count / runtime->channels; |
| return count; |
| } |
| |
| static void mtk_btcvsd_snd_data_transfer(enum bt_sco_direct dir, |
| u8 *src, u8 *dst, |
| unsigned int blk_size, |
| unsigned int blk_num) |
| { |
| unsigned int i, j; |
| |
| if (blk_size == 60 || blk_size == 120 || blk_size == 20) { |
| u32 *src_32 = (u32 *)src; |
| u32 *dst_32 = (u32 *)dst; |
| |
| for (i = 0; i < (blk_size * blk_num / 4); i++) |
| *dst_32++ = *src_32++; |
| } else { |
| u16 *src_16 = (u16 *)src; |
| u16 *dst_16 = (u16 *)dst; |
| |
| for (j = 0; j < blk_num; j++) { |
| for (i = 0; i < (blk_size / 2); i++) |
| *dst_16++ = *src_16++; |
| |
| if (dir == BT_SCO_DIRECT_BT2ARM) |
| src_16++; |
| else |
| dst_16++; |
| } |
| } |
| } |
| |
| /* write encoded mute data to bt sram */ |
| static int btcvsd_tx_clean_buffer(struct mtk_btcvsd_snd *bt) |
| { |
| unsigned int i; |
| unsigned int num_valid_addr; |
| unsigned long flags; |
| enum BT_SCO_BAND band = bt->band; |
| |
| /* prepare encoded mute data */ |
| if (band == BT_SCO_NB) |
| memset(bt->tx->temp_packet_buf, 170, SCO_PACKET_180); |
| else |
| memcpy(bt->tx->temp_packet_buf, |
| table_msbc_silence, SCO_PACKET_180); |
| |
| /* write mute data to bt tx sram buffer */ |
| spin_lock_irqsave(&bt->tx_lock, flags); |
| num_valid_addr = bt->tx->buffer_info.num_valid_addr; |
| |
| dev_info(bt->dev, "%s(), band %d, num_valid_addr %u\n", |
| __func__, band, num_valid_addr); |
| |
| for (i = 0; i < num_valid_addr; i++) { |
| void *dst; |
| |
| dev_info(bt->dev, "%s(), clean addr 0x%lx\n", __func__, |
| bt->tx->buffer_info.bt_sram_addr[i]); |
| |
| dst = (void *)bt->tx->buffer_info.bt_sram_addr[i]; |
| |
| mtk_btcvsd_snd_data_transfer(BT_SCO_DIRECT_ARM2BT, |
| bt->tx->temp_packet_buf, dst, |
| bt->tx->buffer_info.packet_length, |
| bt->tx->buffer_info.packet_num); |
| } |
| spin_unlock_irqrestore(&bt->tx_lock, flags); |
| |
| return 0; |
| } |
| |
| static int mtk_btcvsd_read_from_bt(struct mtk_btcvsd_snd *bt, |
| enum bt_sco_packet_len packet_type, |
| unsigned int packet_length, |
| unsigned int packet_num, |
| unsigned int blk_size, |
| unsigned int control) |
| { |
| unsigned int i; |
| int pv; |
| u8 *src; |
| unsigned int packet_buf_ofs; |
| unsigned long flags; |
| unsigned long connsys_addr_rx, ap_addr_rx; |
| |
| connsys_addr_rx = *bt->bt_reg_pkt_r; |
| ap_addr_rx = (unsigned long)bt->bt_sram_bank2_base + |
| (connsys_addr_rx & 0xFFFF); |
| |
| if (connsys_addr_rx == 0xdeadfeed) { |
| /* bt return 0xdeadfeed if read register during bt sleep */ |
| dev_warn(bt->dev, "%s(), connsys_addr_rx == 0xdeadfeed", |
| __func__); |
| return -EIO; |
| } |
| |
| src = (u8 *)ap_addr_rx; |
| |
| mtk_btcvsd_snd_data_transfer(BT_SCO_DIRECT_BT2ARM, src, |
| bt->rx->temp_packet_buf, packet_length, |
| packet_num); |
| |
| spin_lock_irqsave(&bt->rx_lock, flags); |
| for (i = 0; i < blk_size; i++) { |
| packet_buf_ofs = (bt->rx->packet_w & SCO_RX_PACKET_MASK) * |
| bt->rx->packet_size; |
| memcpy(bt->rx_packet_buf + packet_buf_ofs, |
| bt->rx->temp_packet_buf + (SCO_RX_PLC_SIZE * i), |
| SCO_RX_PLC_SIZE); |
| if ((control & btsco_packet_valid_mask[packet_type][i]) == |
| btsco_packet_valid_mask[packet_type][i]) |
| pv = 1; |
| else |
| pv = 0; |
| |
| packet_buf_ofs += SCO_RX_PLC_SIZE; |
| memcpy(bt->rx_packet_buf + packet_buf_ofs, (void *)&pv, |
| SCO_CVSD_PACKET_VALID_SIZE); |
| bt->rx->packet_w++; |
| } |
| spin_unlock_irqrestore(&bt->rx_lock, flags); |
| return 0; |
| } |
| |
| int mtk_btcvsd_write_to_bt(struct mtk_btcvsd_snd *bt, |
| enum bt_sco_packet_len packet_type, |
| unsigned int packet_length, |
| unsigned int packet_num, |
| unsigned int blk_size) |
| { |
| unsigned int i; |
| unsigned long flags; |
| u8 *dst; |
| unsigned long connsys_addr_tx, ap_addr_tx; |
| bool new_ap_addr_tx = true; |
| |
| connsys_addr_tx = *bt->bt_reg_pkt_w; |
| ap_addr_tx = (unsigned long)bt->bt_sram_bank2_base + |
| (connsys_addr_tx & 0xFFFF); |
| |
| if (connsys_addr_tx == 0xdeadfeed) { |
| /* bt return 0xdeadfeed if read register during bt sleep */ |
| dev_warn(bt->dev, "%s(), connsys_addr_tx == 0xdeadfeed\n", |
| __func__); |
| return -EIO; |
| } |
| |
| spin_lock_irqsave(&bt->tx_lock, flags); |
| for (i = 0; i < blk_size; i++) { |
| memcpy(bt->tx->temp_packet_buf + (bt->tx->packet_size * i), |
| (bt->tx_packet_buf + |
| (bt->tx->packet_r % SCO_TX_PACKER_BUF_NUM) * |
| bt->tx->packet_size), |
| bt->tx->packet_size); |
| |
| bt->tx->packet_r++; |
| } |
| spin_unlock_irqrestore(&bt->tx_lock, flags); |
| |
| dst = (u8 *)ap_addr_tx; |
| |
| if (!bt->tx->mute) { |
| mtk_btcvsd_snd_data_transfer(BT_SCO_DIRECT_ARM2BT, |
| bt->tx->temp_packet_buf, dst, |
| packet_length, packet_num); |
| } |
| |
| /* store bt tx buffer sram info */ |
| bt->tx->buffer_info.packet_length = packet_length; |
| bt->tx->buffer_info.packet_num = packet_num; |
| for (i = 0; i < bt->tx->buffer_info.num_valid_addr; i++) { |
| if (bt->tx->buffer_info.bt_sram_addr[i] == ap_addr_tx) { |
| new_ap_addr_tx = false; |
| break; |
| } |
| } |
| if (new_ap_addr_tx) { |
| unsigned int next_idx; |
| |
| spin_lock_irqsave(&bt->tx_lock, flags); |
| bt->tx->buffer_info.num_valid_addr++; |
| next_idx = bt->tx->buffer_info.num_valid_addr - 1; |
| bt->tx->buffer_info.bt_sram_addr[next_idx] = ap_addr_tx; |
| spin_unlock_irqrestore(&bt->tx_lock, flags); |
| dev_info(bt->dev, "%s(), new ap_addr_tx = 0x%lx, num_valid_addr %d\n", |
| __func__, ap_addr_tx, |
| bt->tx->buffer_info.num_valid_addr); |
| } |
| |
| if (bt->tx->mute) |
| btcvsd_tx_clean_buffer(bt); |
| |
| return 0; |
| } |
| |
| static irqreturn_t mtk_btcvsd_snd_irq_handler(int irq_id, void *dev) |
| { |
| struct mtk_btcvsd_snd *bt = dev; |
| unsigned int packet_type, packet_num, packet_length; |
| unsigned int buf_cnt_tx, buf_cnt_rx, control; |
| |
| if (bt->rx->state != BT_SCO_STATE_RUNNING && |
| bt->rx->state != BT_SCO_STATE_ENDING && |
| bt->tx->state != BT_SCO_STATE_RUNNING && |
| bt->tx->state != BT_SCO_STATE_ENDING) { |
| dev_warn(bt->dev, "%s(), in idle state: rx->state: %d, tx->state: %d\n", |
| __func__, bt->rx->state, bt->tx->state); |
| goto irq_handler_exit; |
| } |
| |
| control = *bt->bt_reg_ctl; |
| packet_type = (control >> 18) & 0x7; |
| |
| if (((control >> 31) & 1) == 0) { |
| dev_warn(bt->dev, "%s(), ((control >> 31) & 1) == 0, control 0x%x\n", |
| __func__, control); |
| goto irq_handler_exit; |
| } |
| |
| if (packet_type >= BT_SCO_CVSD_MAX) { |
| dev_warn(bt->dev, "%s(), invalid packet_type %u, exit\n", |
| __func__, packet_type); |
| goto irq_handler_exit; |
| } |
| |
| packet_length = btsco_packet_info[packet_type][0]; |
| packet_num = btsco_packet_info[packet_type][1]; |
| buf_cnt_tx = btsco_packet_info[packet_type][2]; |
| buf_cnt_rx = btsco_packet_info[packet_type][3]; |
| |
| if (bt->rx->state == BT_SCO_STATE_RUNNING || |
| bt->rx->state == BT_SCO_STATE_ENDING) { |
| if (bt->rx->xrun) { |
| if (bt->rx->packet_w - bt->rx->packet_r <= |
| SCO_RX_PACKER_BUF_NUM - 2 * buf_cnt_rx) { |
| /* |
| * free space is larger then |
| * twice interrupt rx data size |
| */ |
| bt->rx->xrun = 0; |
| dev_warn(bt->dev, "%s(), rx->xrun 0!\n", |
| __func__); |
| } |
| } |
| |
| if (!bt->rx->xrun && |
| (bt->rx->packet_w - bt->rx->packet_r <= |
| SCO_RX_PACKER_BUF_NUM - buf_cnt_rx)) { |
| mtk_btcvsd_read_from_bt(bt, |
| packet_type, |
| packet_length, |
| packet_num, |
| buf_cnt_rx, |
| control); |
| bt->rx->rw_cnt++; |
| } else { |
| bt->rx->xrun = 1; |
| dev_warn(bt->dev, "%s(), rx->xrun 1\n", __func__); |
| } |
| } |
| |
| /* tx */ |
| bt->tx->timeout = 0; |
| if ((bt->tx->state == BT_SCO_STATE_RUNNING || |
| bt->tx->state == BT_SCO_STATE_ENDING) && |
| bt->tx->trigger_start) { |
| if (bt->tx->xrun) { |
| /* prepared data is larger then twice |
| * interrupt tx data size |
| */ |
| if (bt->tx->packet_w - bt->tx->packet_r >= |
| 2 * buf_cnt_tx) { |
| bt->tx->xrun = 0; |
| dev_warn(bt->dev, "%s(), tx->xrun 0\n", |
| __func__); |
| } |
| } |
| |
| if ((!bt->tx->xrun && |
| (bt->tx->packet_w - bt->tx->packet_r >= buf_cnt_tx)) || |
| bt->tx->state == BT_SCO_STATE_ENDING) { |
| mtk_btcvsd_write_to_bt(bt, |
| packet_type, |
| packet_length, |
| packet_num, |
| buf_cnt_tx); |
| bt->tx->rw_cnt++; |
| } else { |
| bt->tx->xrun = 1; |
| dev_warn(bt->dev, "%s(), tx->xrun 1\n", __func__); |
| } |
| } |
| |
| *bt->bt_reg_ctl &= ~BT_CVSD_CLEAR; |
| |
| if (bt->rx->state == BT_SCO_STATE_RUNNING || |
| bt->rx->state == BT_SCO_STATE_ENDING) { |
| bt->rx->wait_flag = 1; |
| wake_up_interruptible(&bt->rx_wait); |
| snd_pcm_period_elapsed(bt->rx->substream); |
| } |
| if (bt->tx->state == BT_SCO_STATE_RUNNING || |
| bt->tx->state == BT_SCO_STATE_ENDING) { |
| bt->tx->wait_flag = 1; |
| wake_up_interruptible(&bt->tx_wait); |
| snd_pcm_period_elapsed(bt->tx->substream); |
| } |
| |
| return IRQ_HANDLED; |
| irq_handler_exit: |
| *bt->bt_reg_ctl &= ~BT_CVSD_CLEAR; |
| return IRQ_HANDLED; |
| } |
| |
| static int wait_for_bt_irq(struct mtk_btcvsd_snd *bt, |
| struct mtk_btcvsd_snd_stream *bt_stream) |
| { |
| unsigned long long t1, t2; |
| /* one interrupt period = 22.5ms */ |
| unsigned long long timeout_limit = 22500000; |
| int max_timeout_trial = 2; |
| int ret; |
| |
| bt_stream->wait_flag = 0; |
| |
| while (max_timeout_trial && !bt_stream->wait_flag) { |
| t1 = sched_clock(); |
| if (bt_stream->stream == SNDRV_PCM_STREAM_PLAYBACK) { |
| ret = wait_event_interruptible_timeout(bt->tx_wait, |
| bt_stream->wait_flag, |
| nsecs_to_jiffies(timeout_limit)); |
| } else { |
| ret = wait_event_interruptible_timeout(bt->rx_wait, |
| bt_stream->wait_flag, |
| nsecs_to_jiffies(timeout_limit)); |
| } |
| |
| t2 = sched_clock(); |
| t2 = t2 - t1; /* in ns (10^9) */ |
| |
| if (t2 > timeout_limit) { |
| dev_warn(bt->dev, "%s(), stream %d, timeout %llu, limit %llu, ret %d, flag %d\n", |
| __func__, bt_stream->stream, |
| t2, timeout_limit, ret, |
| bt_stream->wait_flag); |
| } |
| |
| if (ret < 0) { |
| /* |
| * error, -ERESTARTSYS if it was interrupted by |
| * a signal |
| */ |
| dev_warn(bt->dev, "%s(), stream %d, error, trial left %d\n", |
| __func__, |
| bt_stream->stream, max_timeout_trial); |
| |
| bt_stream->timeout = 1; |
| return ret; |
| } else if (ret == 0) { |
| /* conidtion is false after timeout */ |
| max_timeout_trial--; |
| dev_warn(bt->dev, "%s(), stream %d, error, timeout, condition is false, trial left %d\n", |
| __func__, |
| bt_stream->stream, max_timeout_trial); |
| |
| if (max_timeout_trial <= 0) { |
| bt_stream->timeout = 1; |
| return -ETIME; |
| } |
| } |
| } |
| |
| return 0; |
| } |
| |
| ssize_t mtk_btcvsd_snd_read(struct mtk_btcvsd_snd *bt, |
| char __user *buf, |
| size_t count) |
| { |
| ssize_t read_size = 0, read_count = 0, cur_read_idx, cont; |
| unsigned int cur_buf_ofs = 0; |
| unsigned long avail; |
| unsigned long flags; |
| unsigned int packet_size = bt->rx->packet_size; |
| |
| while (count) { |
| spin_lock_irqsave(&bt->rx_lock, flags); |
| /* available data in RX packet buffer */ |
| avail = (bt->rx->packet_w - bt->rx->packet_r) * packet_size; |
| |
| cur_read_idx = (bt->rx->packet_r & SCO_RX_PACKET_MASK) * |
| packet_size; |
| spin_unlock_irqrestore(&bt->rx_lock, flags); |
| |
| if (!avail) { |
| int ret = wait_for_bt_irq(bt, bt->rx); |
| |
| if (ret) |
| return read_count; |
| |
| continue; |
| } |
| |
| /* count must be multiple of packet_size */ |
| if (count % packet_size != 0 || |
| avail % packet_size != 0) { |
| dev_warn(bt->dev, "%s(), count %zu or d %lu is not multiple of packet_size %dd\n", |
| __func__, count, avail, packet_size); |
| |
| count -= count % packet_size; |
| avail -= avail % packet_size; |
| } |
| |
| if (count > avail) |
| read_size = avail; |
| else |
| read_size = count; |
| |
| /* calculate continue space */ |
| cont = bt->rx->buf_size - cur_read_idx; |
| if (read_size > cont) |
| read_size = cont; |
| |
| if (copy_to_user(buf + cur_buf_ofs, |
| bt->rx_packet_buf + cur_read_idx, |
| read_size)) { |
| dev_warn(bt->dev, "%s(), copy_to_user fail\n", |
| __func__); |
| return -EFAULT; |
| } |
| |
| spin_lock_irqsave(&bt->rx_lock, flags); |
| bt->rx->packet_r += read_size / packet_size; |
| spin_unlock_irqrestore(&bt->rx_lock, flags); |
| |
| read_count += read_size; |
| cur_buf_ofs += read_size; |
| count -= read_size; |
| } |
| |
| /* |
| * save current timestamp & buffer time in times_tamp and |
| * buf_data_equivalent_time |
| */ |
| bt->rx->time_stamp = sched_clock(); |
| bt->rx->buf_data_equivalent_time = |
| (unsigned long long)(bt->rx->packet_w - bt->rx->packet_r) * |
| SCO_RX_PLC_SIZE * 16 * 1000 / 2 / 64; |
| bt->rx->buf_data_equivalent_time += read_count * SCO_RX_PLC_SIZE * |
| 16 * 1000 / packet_size / 2 / 64; |
| /* return equivalent time(us) to data count */ |
| bt->rx->buf_data_equivalent_time *= 1000; |
| |
| return read_count; |
| } |
| |
| ssize_t mtk_btcvsd_snd_write(struct mtk_btcvsd_snd *bt, |
| char __user *buf, |
| size_t count) |
| { |
| int written_size = count, avail = 0, cur_write_idx, write_size, cont; |
| unsigned int cur_buf_ofs = 0; |
| unsigned long flags; |
| unsigned int packet_size = bt->tx->packet_size; |
| |
| /* |
| * save current timestamp & buffer time in time_stamp and |
| * buf_data_equivalent_time |
| */ |
| bt->tx->time_stamp = sched_clock(); |
| bt->tx->buf_data_equivalent_time = |
| (unsigned long long)(bt->tx->packet_w - bt->tx->packet_r) * |
| packet_size * 16 * 1000 / 2 / 64; |
| |
| /* return equivalent time(us) to data count */ |
| bt->tx->buf_data_equivalent_time *= 1000; |
| |
| while (count) { |
| spin_lock_irqsave(&bt->tx_lock, flags); |
| /* free space of TX packet buffer */ |
| avail = bt->tx->buf_size - |
| (bt->tx->packet_w - bt->tx->packet_r) * packet_size; |
| |
| cur_write_idx = (bt->tx->packet_w % SCO_TX_PACKER_BUF_NUM) * |
| packet_size; |
| spin_unlock_irqrestore(&bt->tx_lock, flags); |
| |
| if (!avail) { |
| int ret = wait_for_bt_irq(bt, bt->rx); |
| |
| if (ret) |
| return written_size; |
| |
| continue; |
| } |
| |
| /* count must be multiple of bt->tx->packet_size */ |
| if (count % packet_size != 0 || |
| avail % packet_size != 0) { |
| dev_warn(bt->dev, "%s(), count %zu or avail %d is not multiple of packet_size %d\n", |
| __func__, count, avail, packet_size); |
| count -= count % packet_size; |
| avail -= avail % packet_size; |
| } |
| |
| if (count > avail) |
| write_size = avail; |
| else |
| write_size = count; |
| |
| /* calculate continue space */ |
| cont = bt->tx->buf_size - cur_write_idx; |
| if (write_size > cont) |
| write_size = cont; |
| |
| if (copy_from_user(bt->tx_packet_buf + |
| cur_write_idx, |
| buf + cur_buf_ofs, |
| write_size)) { |
| dev_warn(bt->dev, "%s(), copy_from_user fail\n", |
| __func__); |
| return -EFAULT; |
| } |
| |
| spin_lock_irqsave(&bt->tx_lock, flags); |
| bt->tx->packet_w += write_size / packet_size; |
| spin_unlock_irqrestore(&bt->tx_lock, flags); |
| cur_buf_ofs += write_size; |
| count -= write_size; |
| } |
| |
| return written_size; |
| } |
| |
| static struct mtk_btcvsd_snd_stream *get_bt_stream |
| (struct mtk_btcvsd_snd *bt, struct snd_pcm_substream *substream) |
| { |
| if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) |
| return bt->tx; |
| else |
| return bt->rx; |
| } |
| |
| /* pcm ops */ |
| static const struct snd_pcm_hardware mtk_btcvsd_hardware = { |
| .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED | |
| SNDRV_PCM_INFO_RESUME), |
| .formats = SNDRV_PCM_FMTBIT_S16_LE, |
| .buffer_bytes_max = 24 * 1024, |
| .period_bytes_max = 24 * 1024, |
| .periods_min = 2, |
| .periods_max = 16, |
| .fifo_size = 0, |
| }; |
| |
| static int mtk_pcm_btcvsd_open(struct snd_pcm_substream *substream) |
| { |
| struct snd_soc_pcm_runtime *rtd = substream->private_data; |
| struct snd_soc_component *component = |
| snd_soc_rtdcom_lookup(rtd, BTCVSD_SND_NAME); |
| struct mtk_btcvsd_snd *bt = snd_soc_component_get_drvdata(component); |
| int ret; |
| |
| dev_dbg(bt->dev, "%s(), stream %d, substream %p\n", |
| __func__, substream->stream, substream); |
| |
| snd_soc_set_runtime_hwparams(substream, &mtk_btcvsd_hardware); |
| |
| if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { |
| ret = mtk_btcvsd_snd_tx_init(bt); |
| bt->tx->substream = substream; |
| } else { |
| ret = mtk_btcvsd_snd_rx_init(bt); |
| bt->rx->substream = substream; |
| } |
| |
| return ret; |
| } |
| |
| static int mtk_pcm_btcvsd_close(struct snd_pcm_substream *substream) |
| { |
| struct snd_soc_pcm_runtime *rtd = substream->private_data; |
| struct snd_soc_component *component = |
| snd_soc_rtdcom_lookup(rtd, BTCVSD_SND_NAME); |
| struct mtk_btcvsd_snd *bt = snd_soc_component_get_drvdata(component); |
| struct mtk_btcvsd_snd_stream *bt_stream = get_bt_stream(bt, substream); |
| |
| dev_dbg(bt->dev, "%s(), stream %d\n", __func__, substream->stream); |
| |
| mtk_btcvsd_snd_set_state(bt, bt_stream, BT_SCO_STATE_IDLE); |
| bt_stream->substream = NULL; |
| return 0; |
| } |
| |
| static int mtk_pcm_btcvsd_hw_params(struct snd_pcm_substream *substream, |
| struct snd_pcm_hw_params *hw_params) |
| { |
| struct snd_soc_pcm_runtime *rtd = substream->private_data; |
| struct snd_soc_component *component = |
| snd_soc_rtdcom_lookup(rtd, BTCVSD_SND_NAME); |
| struct mtk_btcvsd_snd *bt = snd_soc_component_get_drvdata(component); |
| |
| if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK && |
| params_buffer_bytes(hw_params) % bt->tx->packet_size != 0) { |
| dev_warn(bt->dev, "%s(), error, buffer size %d not valid\n", |
| __func__, |
| params_buffer_bytes(hw_params)); |
| return -EINVAL; |
| } |
| |
| substream->runtime->dma_bytes = params_buffer_bytes(hw_params); |
| return 0; |
| } |
| |
| static int mtk_pcm_btcvsd_hw_free(struct snd_pcm_substream *substream) |
| { |
| struct snd_soc_pcm_runtime *rtd = substream->private_data; |
| struct snd_soc_component *component = |
| snd_soc_rtdcom_lookup(rtd, BTCVSD_SND_NAME); |
| struct mtk_btcvsd_snd *bt = snd_soc_component_get_drvdata(component); |
| |
| if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) |
| btcvsd_tx_clean_buffer(bt); |
| |
| return 0; |
| } |
| |
| static int mtk_pcm_btcvsd_prepare(struct snd_pcm_substream *substream) |
| { |
| struct snd_soc_pcm_runtime *rtd = substream->private_data; |
| struct snd_soc_component *component = |
| snd_soc_rtdcom_lookup(rtd, BTCVSD_SND_NAME); |
| struct mtk_btcvsd_snd *bt = snd_soc_component_get_drvdata(component); |
| struct mtk_btcvsd_snd_stream *bt_stream = get_bt_stream(bt, substream); |
| |
| dev_dbg(bt->dev, "%s(), stream %d\n", __func__, substream->stream); |
| |
| mtk_btcvsd_snd_set_state(bt, bt_stream, BT_SCO_STATE_RUNNING); |
| return 0; |
| } |
| |
| static int mtk_pcm_btcvsd_trigger(struct snd_pcm_substream *substream, int cmd) |
| { |
| struct snd_soc_pcm_runtime *rtd = substream->private_data; |
| struct snd_soc_component *component = |
| snd_soc_rtdcom_lookup(rtd, BTCVSD_SND_NAME); |
| struct mtk_btcvsd_snd *bt = snd_soc_component_get_drvdata(component); |
| struct mtk_btcvsd_snd_stream *bt_stream = get_bt_stream(bt, substream); |
| int stream = substream->stream; |
| int hw_packet_ptr; |
| |
| dev_dbg(bt->dev, "%s(), stream %d, cmd %d\n", |
| __func__, substream->stream, cmd); |
| |
| switch (cmd) { |
| case SNDRV_PCM_TRIGGER_START: |
| case SNDRV_PCM_TRIGGER_RESUME: |
| hw_packet_ptr = stream == SNDRV_PCM_STREAM_PLAYBACK ? |
| bt_stream->packet_r : bt_stream->packet_w; |
| bt_stream->prev_packet_idx = hw_packet_ptr; |
| bt_stream->prev_frame = 0; |
| bt_stream->trigger_start = 1; |
| return 0; |
| case SNDRV_PCM_TRIGGER_STOP: |
| case SNDRV_PCM_TRIGGER_SUSPEND: |
| bt_stream->trigger_start = 0; |
| mtk_btcvsd_snd_set_state(bt, bt_stream, BT_SCO_STATE_ENDING); |
| return 0; |
| default: |
| return -EINVAL; |
| } |
| } |
| |
| static snd_pcm_uframes_t mtk_pcm_btcvsd_pointer |
| (struct snd_pcm_substream *substream) |
| { |
| struct snd_soc_pcm_runtime *rtd = substream->private_data; |
| struct snd_soc_component *component = |
| snd_soc_rtdcom_lookup(rtd, BTCVSD_SND_NAME); |
| struct mtk_btcvsd_snd *bt = snd_soc_component_get_drvdata(component); |
| struct mtk_btcvsd_snd_stream *bt_stream; |
| snd_pcm_uframes_t frame = 0; |
| int byte = 0; |
| int hw_packet_ptr; |
| int packet_diff; |
| spinlock_t *lock; /* spinlock for bt stream control */ |
| unsigned long flags; |
| |
| if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { |
| lock = &bt->tx_lock; |
| bt_stream = bt->tx; |
| } else { |
| lock = &bt->rx_lock; |
| bt_stream = bt->rx; |
| } |
| |
| spin_lock_irqsave(lock, flags); |
| hw_packet_ptr = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? |
| bt->tx->packet_r : bt->rx->packet_w; |
| |
| /* get packet diff from last time */ |
| if (hw_packet_ptr >= bt_stream->prev_packet_idx) { |
| packet_diff = hw_packet_ptr - bt_stream->prev_packet_idx; |
| } else { |
| /* integer overflow */ |
| packet_diff = (INT_MAX - bt_stream->prev_packet_idx) + |
| (hw_packet_ptr - INT_MIN) + 1; |
| } |
| bt_stream->prev_packet_idx = hw_packet_ptr; |
| |
| /* increased bytes */ |
| byte = packet_diff * bt_stream->packet_size; |
| |
| frame = btcvsd_bytes_to_frame(substream, byte); |
| frame += bt_stream->prev_frame; |
| frame %= substream->runtime->buffer_size; |
| |
| bt_stream->prev_frame = frame; |
| |
| spin_unlock_irqrestore(lock, flags); |
| |
| return frame; |
| } |
| |
| static int mtk_pcm_btcvsd_copy(struct snd_pcm_substream *substream, |
| int channel, unsigned long pos, |
| void __user *buf, unsigned long count) |
| { |
| struct snd_soc_pcm_runtime *rtd = substream->private_data; |
| struct snd_soc_component *component = |
| snd_soc_rtdcom_lookup(rtd, BTCVSD_SND_NAME); |
| struct mtk_btcvsd_snd *bt = snd_soc_component_get_drvdata(component); |
| |
| if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) |
| mtk_btcvsd_snd_write(bt, buf, count); |
| else |
| mtk_btcvsd_snd_read(bt, buf, count); |
| |
| return 0; |
| } |
| |
| static struct snd_pcm_ops mtk_btcvsd_ops = { |
| .open = mtk_pcm_btcvsd_open, |
| .close = mtk_pcm_btcvsd_close, |
| .ioctl = snd_pcm_lib_ioctl, |
| .hw_params = mtk_pcm_btcvsd_hw_params, |
| .hw_free = mtk_pcm_btcvsd_hw_free, |
| .prepare = mtk_pcm_btcvsd_prepare, |
| .trigger = mtk_pcm_btcvsd_trigger, |
| .pointer = mtk_pcm_btcvsd_pointer, |
| .copy_user = mtk_pcm_btcvsd_copy, |
| }; |
| |
| /* kcontrol */ |
| static const char *const btsco_band_str[] = {"NB", "WB"}; |
| |
| static const struct soc_enum btcvsd_enum[] = { |
| SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(btsco_band_str), btsco_band_str), |
| }; |
| |
| static int btcvsd_band_get(struct snd_kcontrol *kcontrol, |
| struct snd_ctl_elem_value *ucontrol) |
| { |
| struct snd_soc_component *cmpnt = snd_soc_kcontrol_component(kcontrol); |
| struct mtk_btcvsd_snd *bt = snd_soc_component_get_drvdata(cmpnt); |
| |
| ucontrol->value.integer.value[0] = bt->band; |
| return 0; |
| } |
| |
| static int btcvsd_band_set(struct snd_kcontrol *kcontrol, |
| struct snd_ctl_elem_value *ucontrol) |
| { |
| struct snd_soc_component *cmpnt = snd_soc_kcontrol_component(kcontrol); |
| struct mtk_btcvsd_snd *bt = snd_soc_component_get_drvdata(cmpnt); |
| struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; |
| |
| if (ucontrol->value.enumerated.item[0] >= e->items) |
| return -EINVAL; |
| |
| bt->band = ucontrol->value.integer.value[0]; |
| dev_dbg(bt->dev, "%s(), band %d\n", __func__, bt->band); |
| return 0; |
| } |
| |
| static int btcvsd_tx_mute_get(struct snd_kcontrol *kcontrol, |
| struct snd_ctl_elem_value *ucontrol) |
| { |
| struct snd_soc_component *cmpnt = snd_soc_kcontrol_component(kcontrol); |
| struct mtk_btcvsd_snd *bt = snd_soc_component_get_drvdata(cmpnt); |
| |
| if (!bt->tx) { |
| ucontrol->value.integer.value[0] = 0; |
| return 0; |
| } |
| |
| ucontrol->value.integer.value[0] = bt->tx->mute; |
| return 0; |
| } |
| |
| static int btcvsd_tx_mute_set(struct snd_kcontrol *kcontrol, |
| struct snd_ctl_elem_value *ucontrol) |
| { |
| struct snd_soc_component *cmpnt = snd_soc_kcontrol_component(kcontrol); |
| struct mtk_btcvsd_snd *bt = snd_soc_component_get_drvdata(cmpnt); |
| |
| if (!bt->tx) |
| return 0; |
| |
| bt->tx->mute = ucontrol->value.integer.value[0]; |
| return 0; |
| } |
| |
| static int btcvsd_rx_irq_received_get(struct snd_kcontrol *kcontrol, |
| struct snd_ctl_elem_value *ucontrol) |
| { |
| struct snd_soc_component *cmpnt = snd_soc_kcontrol_component(kcontrol); |
| struct mtk_btcvsd_snd *bt = snd_soc_component_get_drvdata(cmpnt); |
| |
| if (!bt->rx) |
| return 0; |
| |
| ucontrol->value.integer.value[0] = bt->rx->rw_cnt ? 1 : 0; |
| return 0; |
| } |
| |
| static int btcvsd_rx_timeout_get(struct snd_kcontrol *kcontrol, |
| struct snd_ctl_elem_value *ucontrol) |
| { |
| struct snd_soc_component *cmpnt = snd_soc_kcontrol_component(kcontrol); |
| struct mtk_btcvsd_snd *bt = snd_soc_component_get_drvdata(cmpnt); |
| |
| if (!bt->rx) |
| return 0; |
| |
| ucontrol->value.integer.value[0] = bt->rx->timeout; |
| bt->rx->timeout = 0; |
| return 0; |
| } |
| |
| static int btcvsd_rx_timestamp_get(struct snd_kcontrol *kcontrol, |
| unsigned int __user *data, unsigned int size) |
| { |
| struct snd_soc_component *cmpnt = snd_soc_kcontrol_component(kcontrol); |
| struct mtk_btcvsd_snd *bt = snd_soc_component_get_drvdata(cmpnt); |
| int ret = 0; |
| struct mtk_btcvsd_snd_time_buffer_info time_buffer_info_rx; |
| |
| if (size > sizeof(struct mtk_btcvsd_snd_time_buffer_info)) |
| return -EINVAL; |
| |
| get_rx_time_stamp(bt, &time_buffer_info_rx); |
| |
| dev_dbg(bt->dev, "%s(), time_stamp_us %llu, data_count_equi_time %llu", |
| __func__, |
| time_buffer_info_rx.time_stamp_us, |
| time_buffer_info_rx.data_count_equi_time); |
| |
| if (copy_to_user(data, &time_buffer_info_rx, |
| sizeof(struct mtk_btcvsd_snd_time_buffer_info))) { |
| dev_warn(bt->dev, "%s(), copy_to_user fail", __func__); |
| ret = -EFAULT; |
| } |
| |
| return ret; |
| } |
| |
| static int btcvsd_tx_irq_received_get(struct snd_kcontrol *kcontrol, |
| struct snd_ctl_elem_value *ucontrol) |
| { |
| struct snd_soc_component *cmpnt = snd_soc_kcontrol_component(kcontrol); |
| struct mtk_btcvsd_snd *bt = snd_soc_component_get_drvdata(cmpnt); |
| |
| if (!bt->tx) |
| return 0; |
| |
| ucontrol->value.integer.value[0] = bt->tx->rw_cnt ? 1 : 0; |
| return 0; |
| } |
| |
| static int btcvsd_tx_timeout_get(struct snd_kcontrol *kcontrol, |
| struct snd_ctl_elem_value *ucontrol) |
| { |
| struct snd_soc_component *cmpnt = snd_soc_kcontrol_component(kcontrol); |
| struct mtk_btcvsd_snd *bt = snd_soc_component_get_drvdata(cmpnt); |
| |
| ucontrol->value.integer.value[0] = bt->tx->timeout; |
| return 0; |
| } |
| |
| static int btcvsd_tx_timestamp_get(struct snd_kcontrol *kcontrol, |
| unsigned int __user *data, unsigned int size) |
| { |
| struct snd_soc_component *cmpnt = snd_soc_kcontrol_component(kcontrol); |
| struct mtk_btcvsd_snd *bt = snd_soc_component_get_drvdata(cmpnt); |
| int ret = 0; |
| struct mtk_btcvsd_snd_time_buffer_info time_buffer_info_tx; |
| |
| if (size > sizeof(struct mtk_btcvsd_snd_time_buffer_info)) |
| return -EINVAL; |
| |
| get_tx_time_stamp(bt, &time_buffer_info_tx); |
| |
| dev_dbg(bt->dev, "%s(), time_stamp_us %llu, data_count_equi_time %llu", |
| __func__, |
| time_buffer_info_tx.time_stamp_us, |
| time_buffer_info_tx.data_count_equi_time); |
| |
| if (copy_to_user(data, &time_buffer_info_tx, |
| sizeof(struct mtk_btcvsd_snd_time_buffer_info))) { |
| dev_warn(bt->dev, "%s(), copy_to_user fail", __func__); |
| ret = -EFAULT; |
| } |
| |
| return ret; |
| } |
| |
| static const struct snd_kcontrol_new mtk_btcvsd_snd_controls[] = { |
| SOC_ENUM_EXT("BTCVSD Band", btcvsd_enum[0], |
| btcvsd_band_get, btcvsd_band_set), |
| SOC_SINGLE_BOOL_EXT("BTCVSD Tx Mute Switch", 0, |
| btcvsd_tx_mute_get, btcvsd_tx_mute_set), |
| SOC_SINGLE_BOOL_EXT("BTCVSD Tx Irq Received Switch", 0, |
| btcvsd_tx_irq_received_get, NULL), |
| SOC_SINGLE_BOOL_EXT("BTCVSD Tx Timeout Switch", 0, |
| btcvsd_tx_timeout_get, NULL), |
| SOC_SINGLE_BOOL_EXT("BTCVSD Rx Irq Received Switch", 0, |
| btcvsd_rx_irq_received_get, NULL), |
| SOC_SINGLE_BOOL_EXT("BTCVSD Rx Timeout Switch", 0, |
| btcvsd_rx_timeout_get, NULL), |
| SND_SOC_BYTES_TLV("BTCVSD Rx Timestamp", |
| sizeof(struct mtk_btcvsd_snd_time_buffer_info), |
| btcvsd_rx_timestamp_get, NULL), |
| SND_SOC_BYTES_TLV("BTCVSD Tx Timestamp", |
| sizeof(struct mtk_btcvsd_snd_time_buffer_info), |
| btcvsd_tx_timestamp_get, NULL), |
| }; |
| |
| static int mtk_btcvsd_snd_component_probe(struct snd_soc_component *component) |
| { |
| return snd_soc_add_component_controls(component, |
| mtk_btcvsd_snd_controls, |
| ARRAY_SIZE(mtk_btcvsd_snd_controls)); |
| } |
| |
| static const struct snd_soc_component_driver mtk_btcvsd_snd_platform = { |
| .name = BTCVSD_SND_NAME, |
| .ops = &mtk_btcvsd_ops, |
| .probe = mtk_btcvsd_snd_component_probe, |
| }; |
| |
| static int mtk_btcvsd_snd_probe(struct platform_device *pdev) |
| { |
| int ret = 0; |
| int irq_id; |
| u32 offset[5] = {0, 0, 0, 0, 0}; |
| struct mtk_btcvsd_snd *btcvsd; |
| struct device *dev = &pdev->dev; |
| |
| /* init btcvsd private data */ |
| btcvsd = devm_kzalloc(dev, sizeof(*btcvsd), GFP_KERNEL); |
| if (!btcvsd) |
| return -ENOMEM; |
| platform_set_drvdata(pdev, btcvsd); |
| btcvsd->dev = dev; |
| |
| /* init tx/rx */ |
| btcvsd->rx = devm_kzalloc(btcvsd->dev, sizeof(*btcvsd->rx), GFP_KERNEL); |
| if (!btcvsd->rx) |
| return -ENOMEM; |
| |
| btcvsd->tx = devm_kzalloc(btcvsd->dev, sizeof(*btcvsd->tx), GFP_KERNEL); |
| if (!btcvsd->tx) |
| return -ENOMEM; |
| |
| spin_lock_init(&btcvsd->tx_lock); |
| spin_lock_init(&btcvsd->rx_lock); |
| |
| init_waitqueue_head(&btcvsd->tx_wait); |
| init_waitqueue_head(&btcvsd->rx_wait); |
| |
| mtk_btcvsd_snd_tx_init(btcvsd); |
| mtk_btcvsd_snd_rx_init(btcvsd); |
| |
| /* irq */ |
| irq_id = platform_get_irq(pdev, 0); |
| if (irq_id <= 0) { |
| dev_err(dev, "%pOFn no irq found\n", dev->of_node); |
| return irq_id < 0 ? irq_id : -ENXIO; |
| } |
| |
| ret = devm_request_irq(dev, irq_id, mtk_btcvsd_snd_irq_handler, |
| IRQF_TRIGGER_LOW, "BTCVSD_ISR_Handle", |
| (void *)btcvsd); |
| if (ret) { |
| dev_err(dev, "could not request_irq for BTCVSD_ISR_Handle\n"); |
| return ret; |
| } |
| |
| btcvsd->irq_id = irq_id; |
| |
| /* iomap */ |
| btcvsd->bt_pkv_base = of_iomap(dev->of_node, 0); |
| if (!btcvsd->bt_pkv_base) { |
| dev_err(dev, "iomap bt_pkv_base fail\n"); |
| return -EIO; |
| } |
| |
| btcvsd->bt_sram_bank2_base = of_iomap(dev->of_node, 1); |
| if (!btcvsd->bt_sram_bank2_base) { |
| dev_err(dev, "iomap bt_sram_bank2_base fail\n"); |
| return -EIO; |
| } |
| |
| btcvsd->infra = syscon_regmap_lookup_by_phandle(dev->of_node, |
| "mediatek,infracfg"); |
| if (IS_ERR(btcvsd->infra)) { |
| dev_err(dev, "cannot find infra controller: %ld\n", |
| PTR_ERR(btcvsd->infra)); |
| return PTR_ERR(btcvsd->infra); |
| } |
| |
| /* get offset */ |
| ret = of_property_read_u32_array(dev->of_node, "mediatek,offset", |
| offset, |
| ARRAY_SIZE(offset)); |
| if (ret) { |
| dev_warn(dev, "%s(), get offset fail, ret %d\n", __func__, ret); |
| return ret; |
| } |
| btcvsd->infra_misc_offset = offset[0]; |
| btcvsd->conn_bt_cvsd_mask = offset[1]; |
| btcvsd->cvsd_mcu_read_offset = offset[2]; |
| btcvsd->cvsd_mcu_write_offset = offset[3]; |
| btcvsd->cvsd_packet_indicator = offset[4]; |
| |
| btcvsd->bt_reg_pkt_r = btcvsd->bt_pkv_base + |
| btcvsd->cvsd_mcu_read_offset; |
| btcvsd->bt_reg_pkt_w = btcvsd->bt_pkv_base + |
| btcvsd->cvsd_mcu_write_offset; |
| btcvsd->bt_reg_ctl = btcvsd->bt_pkv_base + |
| btcvsd->cvsd_packet_indicator; |
| |
| /* init state */ |
| mtk_btcvsd_snd_set_state(btcvsd, btcvsd->tx, BT_SCO_STATE_IDLE); |
| mtk_btcvsd_snd_set_state(btcvsd, btcvsd->rx, BT_SCO_STATE_IDLE); |
| |
| return devm_snd_soc_register_component(dev, &mtk_btcvsd_snd_platform, |
| NULL, 0); |
| } |
| |
| static int mtk_btcvsd_snd_remove(struct platform_device *pdev) |
| { |
| struct mtk_btcvsd_snd *btcvsd = dev_get_drvdata(&pdev->dev); |
| |
| iounmap(btcvsd->bt_pkv_base); |
| iounmap(btcvsd->bt_sram_bank2_base); |
| return 0; |
| } |
| |
| static const struct of_device_id mtk_btcvsd_snd_dt_match[] = { |
| { .compatible = "mediatek,mtk-btcvsd-snd", }, |
| {}, |
| }; |
| MODULE_DEVICE_TABLE(of, mtk_btcvsd_snd_dt_match); |
| |
| static struct platform_driver mtk_btcvsd_snd_driver = { |
| .driver = { |
| .name = "mtk-btcvsd-snd", |
| .of_match_table = mtk_btcvsd_snd_dt_match, |
| }, |
| .probe = mtk_btcvsd_snd_probe, |
| .remove = mtk_btcvsd_snd_remove, |
| }; |
| |
| module_platform_driver(mtk_btcvsd_snd_driver); |
| |
| MODULE_DESCRIPTION("Mediatek ALSA BT SCO CVSD/MSBC Driver"); |
| MODULE_AUTHOR("KaiChieh Chuang <kaichieh.chuang@mediatek.com>"); |
| MODULE_LICENSE("GPL v2"); |