| /* |
| * drivers/amlogic/audiodsp/dsp_mailbox.c |
| * |
| * Copyright (C) 2017 Amlogic, Inc. 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 as published by |
| * the Free Software Foundation; either version 2 of the License, or |
| * (at your option) any later version. |
| * |
| * 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. |
| * |
| */ |
| |
| #define pr_fmt(fmt) "audio_dsp: " fmt |
| |
| #include <linux/version.h> |
| #include <linux/module.h> |
| #include <linux/kernel.h> |
| #include <linux/string.h> |
| #include <linux/io.h> |
| #include <linux/mm.h> |
| #include <linux/interrupt.h> |
| #include <linux/mutex.h> |
| #include <linux/device.h> |
| #include <linux/workqueue.h> |
| #include <linux/delay.h> |
| |
| #include <linux/timer.h> |
| |
| #include <asm/cacheflush.h> |
| |
| #include <linux/amlogic/amports/tsync.h> |
| #include <linux/amlogic/amports/timestamp.h> |
| #include "dsp_mailbox.h" |
| #include "dsp_codec.h" |
| |
| #define BASE_IRQ (32) |
| |
| #define AM_IRQ(reg) (reg + BASE_IRQ) |
| #define INT_ASSIST_MBOX1 AM_IRQ(146) |
| |
| static struct audiodsp_work_t { |
| char *buf; |
| struct work_struct audiodsp_workqueue; |
| } audiodsp_work; |
| |
| int dsp_mailbox_send(struct audiodsp_priv *priv, int overwrite, int num, |
| int cmd, const char *data, int len) |
| { |
| unsigned long flags; |
| int res = -1; |
| struct mail_msg *m; |
| dma_addr_t buf_map; |
| |
| m = &priv->mailbox_reg2[num]; |
| |
| local_irq_save(flags); |
| if (overwrite || m->status == 0) { |
| m->cmd = cmd; |
| m->data = (char *)ARM_2_ARC_ADDR_SWAP((unsigned int)data); |
| m->len = len; |
| m->status = 1; |
| after_change_mailbox(m); |
| if (data != NULL && len > 0) { |
| buf_map = |
| dma_map_single(NULL, (void *)data, len, |
| DMA_TO_DEVICE); |
| dma_unmap_single(NULL, buf_map, len, DMA_TO_DEVICE); |
| } |
| MAIBOX2_IRQ_ENABLE(num); |
| DSP_TRIGGER_IRQ(num); |
| res = 0; |
| } |
| local_irq_restore(flags); |
| return res; |
| } |
| |
| int get_mailbox_data(struct audiodsp_priv *priv, int num, struct mail_msg *msg) |
| { |
| unsigned long flags; |
| dma_addr_t buf_map; |
| struct mail_msg *m; |
| |
| if (num > 31 || num < 0) |
| return -1; |
| local_irq_save(flags); |
| m = &priv->mailbox_reg[num]; |
| pre_read_mailbox(m); |
| /* dsp_addr_map = |
| * dma_map_single(priv->dev,(void*)m, |
| * sizeof(*m),DMA_FROM_DEVICE); |
| * dma_unmap_single(priv->dev,dsp_addr_map, |
| * sizeof(*m),DMA_FROM_DEVICE); |
| */ |
| msg->cmd = m->cmd; |
| msg->data = m->data; |
| msg->data = (char *)((unsigned int)msg->data + AUDIO_DSP_START_ADDR); |
| msg->status = m->status; |
| msg->len = m->len; |
| if (msg->len && msg->data != NULL) { |
| buf_map = |
| dma_map_single(priv->dev, (void *)msg->data, msg->len, |
| DMA_FROM_DEVICE); |
| dma_unmap_single(priv->dev, buf_map, msg->len, DMA_FROM_DEVICE); |
| } |
| m->status = 0; |
| after_change_mailbox(m); |
| local_irq_restore(flags); |
| return 0; |
| } |
| |
| static irqreturn_t audiodsp_mailbox_irq(int irq, void *data) |
| { |
| struct audiodsp_priv *priv = (struct audiodsp_priv *)data; |
| unsigned long status; |
| struct mail_msg msg; |
| int i = 0; |
| unsigned long fiq_mask; |
| |
| status = READ_VREG(MB1_REG); |
| fiq_mask = READ_VREG(MB1_SEL); |
| status = status & fiq_mask; |
| |
| if (status & (1 << M1B_IRQ0_PRINT)) { |
| get_mailbox_data(priv, M1B_IRQ0_PRINT, &msg); |
| SYS_CLEAR_IRQ(M1B_IRQ0_PRINT); |
| /* inv_dcache_range((unsigned long )msg.data, |
| * (unsigned long)msg.data+msg.len); |
| */ |
| |
| DSP_PRNT("%s", msg.data); |
| /* audiodsp_work.buf = msg.data; */ |
| /* schedule_work(&audiodsp_work.audiodsp_workqueue); */ |
| } |
| if (status & (1 << M1B_IRQ1_BUF_OVERFLOW)) { |
| SYS_CLEAR_IRQ(M1B_IRQ1_BUF_OVERFLOW); |
| DSP_PRNT("DSP BUF over flow\n"); |
| } |
| if (status & (1 << M1B_IRQ2_BUF_UNDERFLOW)) { |
| SYS_CLEAR_IRQ(M1B_IRQ2_BUF_UNDERFLOW); |
| DSP_PRNT("DSP BUF over flow\n"); |
| } |
| if (status & (1 << M1B_IRQ3_DECODE_ERROR)) { |
| SYS_CLEAR_IRQ(M1B_IRQ3_DECODE_ERROR); |
| priv->decode_error_count++; |
| } |
| if (status & (1 << M1B_IRQ4_DECODE_FINISH_FRAME)) { |
| struct frame_info *info; |
| |
| SYS_CLEAR_IRQ(M1B_IRQ4_DECODE_FINISH_FRAME); |
| get_mailbox_data(priv, M1B_IRQ4_DECODE_FINISH_FRAME, &msg); |
| info = (struct frame_info *)msg.data; |
| if (info != NULL) { |
| priv->cur_frame_info.offset = info->offset; |
| priv->cur_frame_info.buffered_len = info->buffered_len; |
| } |
| priv->decoded_nb_frames++; |
| complete(&priv->decode_completion); |
| } |
| if (status & (1 << M1B_IRQ5_STREAM_FMT_CHANGED)) { |
| struct frame_fmt *fmt; |
| |
| SYS_CLEAR_IRQ(M1B_IRQ5_STREAM_FMT_CHANGED); |
| get_mailbox_data(priv, M1B_IRQ5_STREAM_FMT_CHANGED, &msg); |
| fmt = (void *)msg.data; |
| /* DSP_PRNT("frame format changed"); */ |
| if (fmt == NULL || (sizeof(struct frame_fmt) < msg.len)) { |
| DSP_PRNT("frame format message error\n"); |
| } else { |
| DSP_PRNT("frame format changed,fmt->valid 0x%x\n", |
| fmt->valid); |
| if (fmt->valid & SUB_FMT_VALID) { |
| priv->frame_format.sub_fmt = fmt->sub_fmt; |
| priv->frame_format.valid |= SUB_FMT_VALID; |
| } |
| if (fmt->valid & CHANNEL_VALID) { |
| priv->frame_format.channel_num = |
| ((fmt->channel_num > |
| 2) ? 2 : (fmt->channel_num)); |
| priv->frame_format.valid |= CHANNEL_VALID; |
| } |
| if (fmt->valid & SAMPLE_RATE_VALID) { |
| priv->frame_format.sample_rate = |
| fmt->sample_rate; |
| priv->frame_format.valid |= SAMPLE_RATE_VALID; |
| } |
| if (fmt->valid & DATA_WIDTH_VALID) { |
| priv->frame_format.data_width = fmt->data_width; |
| priv->frame_format.valid |= DATA_WIDTH_VALID; |
| } |
| } |
| /* |
| * if(fmt->data.pcm_encoded_info){ |
| * set_pcminfo_data(fmt->data.pcm_encoded_info); |
| * } |
| */ |
| DSP_PRNT("audio info from dsp:sample_rate=%d channel_num=%d\n", |
| priv->frame_format.sample_rate, |
| priv->frame_format.channel_num); |
| } |
| if (status & (1 << M1B_IRQ8_IEC958_INFO)) { |
| struct digit_raw_output_info *info; |
| |
| SYS_CLEAR_IRQ(M1B_IRQ8_IEC958_INFO); |
| get_mailbox_data(priv, M1B_IRQ8_IEC958_INFO, &msg); |
| info = (void *)msg.data; |
| #if 1 |
| IEC958_bpf = info->bpf; |
| IEC958_brst = info->brst; |
| IEC958_length = info->length; |
| IEC958_padsize = info->padsize; |
| IEC958_mode = info->mode; |
| IEC958_syncword1 = info->syncword1; |
| IEC958_syncword2 = info->syncword2; |
| IEC958_syncword3 = info->syncword3; |
| IEC958_syncword1_mask = info->syncword1_mask; |
| IEC958_syncword2_mask = info->syncword2_mask; |
| IEC958_syncword3_mask = info->syncword3_mask; |
| IEC958_chstat0_l = info->chstat0_l; |
| IEC958_chstat0_r = info->chstat0_r; |
| IEC958_chstat1_l = info->chstat1_l; |
| IEC958_chstat1_r = info->chstat1_r; |
| #endif |
| /* |
| * IEC958_mode_codec = info->can_bypass; |
| */ |
| |
| DSP_PRNT("MAILBOX: got IEC958 info\n"); |
| /* schedule_work(&audiodsp_work.audiodsp_workqueue); */ |
| } |
| |
| if (status & (1 << M1B_IRQ5_STREAM_RD_WD_TEST)) { |
| DSP_WD((0x84100000 - 4096 + 20 * 20), 0); |
| SYS_CLEAR_IRQ(M1B_IRQ5_STREAM_RD_WD_TEST); |
| get_mailbox_data(priv, M1B_IRQ5_STREAM_RD_WD_TEST, &msg); |
| |
| for (i = 0; i < 12; i++) { |
| if ((DSP_RD((0x84100000 - 512 * 1024 + i * 20))) != |
| (0xff00 | i)) { |
| DSP_PRNT |
| ("a9 read dsp err, now %#lx, should be %#x\n", |
| (DSP_RD((0x84100000 - 512 * 1024 + i * 20))), |
| 12 - i); |
| } |
| /* DSP_PRNT("A9 audio dsp reg%d value 0x%x\n", |
| * i,DSP_RD((0x84100000-4096+i*20))); |
| */ |
| } |
| for (i = 0; i < 12; i++) { |
| DSP_WD((0x84100000 - 512 * 1024 + i * 20), |
| (i % 2) ? i : (0xf0 | i)); |
| |
| } |
| DSP_WD((0x84100000 - 512 * 1024 + 20 * 20), DSP_STATUS_HALT); |
| /* DSP_PRNT("A9 mail box handle finished\n"); |
| * dsp_mailbox_send(priv, 1, |
| * M1B_IRQ5_STREAM_RD_WD_TEST, 0, NULL,0); |
| */ |
| } |
| |
| if (status & (1 << M1B_IRQ7_DECODE_FATAL_ERR)) { |
| int err_code; |
| |
| SYS_CLEAR_IRQ(M1B_IRQ7_DECODE_FATAL_ERR); |
| get_mailbox_data(priv, M1B_IRQ7_DECODE_FATAL_ERR, &msg); |
| |
| err_code = msg.cmd; |
| priv->decode_fatal_err = err_code; |
| |
| if (err_code & 0x01) { |
| timestamp_pcrscr_set(timestamp_vpts_get()); |
| timestamp_pcrscr_enable(1); |
| } else if (err_code & 0x02) { |
| pr_info("Set decode_fatal_err flag, Reset audiodsp!\n"); |
| } |
| } |
| |
| return 0; |
| } |
| |
| static void audiodsp_mailbox_work_queue(struct work_struct *work) |
| { |
| struct audiodsp_work_t *pwork = |
| container_of(work, struct audiodsp_work_t, audiodsp_workqueue); |
| char *message; |
| |
| if (pwork) { |
| message = pwork->buf; |
| if (message) |
| pr_info("%s", message); |
| else |
| pr_info("the message from mailbox is NULL\n"); |
| } else { |
| pr_info("the work queue for audiodsp mailbox is empty\n"); |
| } |
| } |
| |
| int audiodsp_init_mailbox(struct audiodsp_priv *priv) |
| { |
| int err = 0; |
| #if 1 /* MESON_CPU_TYPE >= MESON_CPU_TYPE_MESON8 */ |
| err = request_irq(INT_ASSIST_MBOX1, audiodsp_mailbox_irq, |
| IRQF_SHARED, "audiodsp_mailbox", (void *)priv); |
| #else |
| pr_info("audiodsp request irq not ready\n"); |
| /* err = request_irq(INT_MAILBOX_1B, audiodsp_mailbox_irq, */ |
| /* IRQF_SHARED, "audiodsp_mailbox", (void *)priv); */ |
| #endif |
| if (err != 0) { |
| pr_info("request IRQ for dsp mailbox failed: errno = %x\n", |
| err); |
| return -1; |
| } |
| /* WRITE_MPEG_REG(ASSIST_MBOX0_MASK, 0xffffffff); */ |
| priv->mailbox_reg = (struct mail_msg *)MAILBOX1_REG(0); |
| priv->mailbox_reg2 = (struct mail_msg *)MAILBOX2_REG(0); |
| |
| INIT_WORK(&audiodsp_work.audiodsp_workqueue, |
| audiodsp_mailbox_work_queue); |
| return 0; |
| } |
| |
| int audiodsp_get_audioinfo(struct audiodsp_priv *priv) |
| { |
| int ret = -1; |
| int audio_info = 0; |
| |
| audio_info = DSP_RD(DSP_AUDIO_FORMAT_INFO); |
| if (priv->frame_format.valid == |
| (CHANNEL_VALID | DATA_WIDTH_VALID | SAMPLE_RATE_VALID)) { |
| ret = 0; |
| goto exit; |
| } else if (audio_info) { |
| priv->frame_format.channel_num = audio_info & 0xf; |
| if (priv->frame_format.channel_num) |
| priv->frame_format.valid |= CHANNEL_VALID; |
| priv->frame_format.data_width = (audio_info >> 4) & 0x3f; |
| if (priv->frame_format.data_width) |
| priv->frame_format.valid |= DATA_WIDTH_VALID; |
| priv->frame_format.sample_rate = (audio_info >> 10); |
| if (priv->frame_format.sample_rate) |
| priv->frame_format.valid |= SAMPLE_RATE_VALID; |
| ret = 0; |
| } |
| if (ret == 0) { |
| DSP_PRNT(" audiodsp got audioinfo:[ch num %d],[sr %d]", |
| priv->frame_format.channel_num, |
| priv->frame_format.sample_rate); |
| } |
| exit: |
| return ret; |
| } |
| |
| int audiodsp_release_mailbox(struct audiodsp_priv *priv) |
| { |
| #if 0 /* MESON_CPU_TYPE >= MESON_CPU_TYPE_MESON8 */ |
| free_irq(INT_ASSIST_MBOX1, (void *)priv); |
| #else |
| /* free_irq(INT_MAILBOX_1B,(void *)priv); */ |
| #endif |
| return 0; |
| } |
| |
| static unsigned int hdmi_sr; |
| int mailbox_send_audiodsp(int overwrite, int num, int cmd, const char *data, |
| int len) |
| { |
| int res = -1; |
| int time_out = 0; |
| |
| if (DSP_RD(DSP_STATUS) != DSP_STATUS_RUNNING) { |
| pr_info("fatal error,dsp must be running before mailbox sent\n"); |
| return -1; |
| } |
| DSP_WD(DSP_GET_EXTRA_INFO_FINISH, 0); |
| while (time_out++ < 10) { |
| if ((num == M2B_IRQ0_DSP_AUDIO_EFFECT) |
| && (cmd == DSP_CMD_SET_HDMI_SR)) { |
| hdmi_sr = *(unsigned int *)data; |
| DSP_PRNT("<hdmi to dsp mailbox> sr changed to %d\n", |
| hdmi_sr); |
| DSP_WD(DSP_HDMI_SR, hdmi_sr); |
| res = |
| dsp_mailbox_send(audiodsp_privdata(), overwrite, |
| num, cmd, (char *)&hdmi_sr, |
| sizeof(unsigned int)); |
| break; |
| } |
| { |
| res = |
| dsp_mailbox_send(audiodsp_privdata(), overwrite, |
| num, cmd, data, len); |
| mdelay(10); |
| } |
| if (DSP_RD(DSP_GET_EXTRA_INFO_FINISH) == 0x12345678) |
| break; |
| } |
| if (time_out == 10) { |
| pr_info("error,dsp transfer mailbox time out\n"); |
| return -1; |
| } |
| return res; |
| } |
| EXPORT_SYMBOL(mailbox_send_audiodsp); |