sdk-hwV1.3/lichee/linux-4.9/sound/soc/sunxi/sunxi-mad.c

1806 lines
48 KiB
C

/*
* sound\soc\sunxi\sunxi-mad.c
* (C) Copyright 2018-2023
* AllWinner Technology Co., Ltd. <www.allwinnertech.com>
* wolfgang <qinzhenying@allwinnertech.com>
* yumingfeng <yumingfeng@allwinnertech.com>
*
* some simple description for this code
*
* 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.
*
*/
#include <linux/init.h>
#include <linux/module.h>
#include <linux/device.h>
#include <linux/pm.h>
#include <linux/io.h>
#include <linux/delay.h>
#include <linux/clk.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/of_address.h>
#include <linux/regmap.h>
#include <linux/input.h>
#include <sound/soc.h>
#include <linux/dma/sunxi-dma.h>
#include "sunxi-mad.h"
#include <linux/etherdevice.h>
#include <linux/netdevice.h>
#include <net/netlink.h>
#include <net/sock.h>
#include <linux/workqueue.h>
#include <linux/types.h>
#include <net/net_namespace.h>
#include <net/netns/generic.h>
#include <linux/pm_wakeirq.h>
#include <linux/pm_domain.h>
#define DRV_NAME "sunxi-mad"
/****************************************************************************/
#ifdef SUNXI_MAD_NETLINK_USE
enum sunxi_mad_netlink_status {
SNDRV_MAD_NETLINK_NULL = -1,
SNDRV_MAD_NETLINK_CLOSE = 0,
SNDRV_MAD_NETLINK_START = 1,
SNDRV_MAD_NETLINK_PAUSE = 2,
SNDRV_MAD_NETLINK_RESUME = 3,
SNDRV_MAD_NETLINK_SUSPEND = 4,
};
struct mad_netlink_status {
int pid;
int status;
struct mutex mutex_lock;
};
struct sunxi_mad_event {
struct sock *sock;
struct mad_netlink_status mad_netlink;
};
static struct sunxi_mad_event mad_event;
void sunxi_mad_netlink_send(struct sunxi_mad_event *mad_event, const char *fmt, ...);
#endif
/****************************************************************************/
/*memory mapping*/
#define MAD_SRAM_DMA_SRC_ADDR 0x05480000
/* 128k bytes */
#define MAD_SRAM_SIZE_VALUE 0x80
struct sunxi_mad_sram_size {
unsigned int src_chan_num;
unsigned int standby_chan_num; /* no used */
unsigned int size;
unsigned int sram_store_th;
/* align dma block size */
unsigned int ahb1_rx_th;
unsigned int ahb1_tx_th;
};
/*
* for alignment data when overflow.
* Tips: sram dma width is 32bit.
*/
static const struct sunxi_mad_sram_size mad_sram_size[] = {
{1, 1, 128, 64, 64, 64},
{2, 2, 128, 64, 64, 64},
{3, 3, 120, 60, 60, 60}, /* 120 % (3 * 2 * 2) == 0 */
{4, 4, 128, 64, 64, 64}, /* 128 % (4 * 2) == 0 */
{5, 5, 120, 60, 60, 60}, /* 120 % (5 * 2 * 2) == 0 */
{6, 6, 120, 60, 60, 60}, /* 120 % (6 * 2) == 0 */
{7, 7, 112, 56, 56, 56}, /* 112 % (7 * 2 * 2) == 0 */
{8, 8, 128, 64, 64, 64}, /* 128 % (8 * 2) == 0 */
};
struct sunxi_mad_info *sunxi_mad;
/****************************************************************************/
/*
* UNIT: audio frames
* defalut_val:[0x5]
*/
static int sram_wake_back_da_val = 0x5;
module_param(sram_wake_back_da_val, int, 0644);
MODULE_PARM_DESC(sram_wake_back_da_val, "sunxi mad sram wakeup back debug");
/****************************************************************************/
/* lpsd ad sync frame */
static int lpsd_ad_sync = 0x20;
module_param(lpsd_ad_sync, int, 0644);
MODULE_PARM_DESC(lpsd_ad_sync, "sunxi mad lpsd ad sync debug");
/* lpsd th */
static int lpsd_th = 0x4b0;
module_param(lpsd_th, int, 0644);
MODULE_PARM_DESC(lpsd_th, "sunxi mad lpsd th debug(0x0-0xFFFF)");
/* speed deep on lpsd clk */
static int lpsd_rrun = 0x91;
module_param(lpsd_rrun, int, 0644);
MODULE_PARM_DESC(lpsd_rrun, "sunxi mad lpsd start run debug(0x0-0xFF)");
/* speed deep on lpsd clk */
static int lpsd_rstop = 0xaa;
module_param(lpsd_rstop, int, 0644);
MODULE_PARM_DESC(lpsd_rstop, "sunxi mad about lpsd stop run debug(0x0-0xFF)");
/* frames */
static int lpsd_ecnt = 2;//default: 0x32;
module_param(lpsd_ecnt, int, 0644);
MODULE_PARM_DESC(lpsd_ecnt, "sunxi mad about lpsd end count debug(0x0-0xFFFF)");
/****************************************************************************/
void sunxi_mad_lpsd_init(void)
{
regmap_write(sunxi_mad->regmap, SUNXI_MAD_SRAM_WAKE_BACK_DATA,
sram_wake_back_da_val);
regmap_write(sunxi_mad->regmap, SUNXI_MAD_LPSD_AD_SYNC_FC, lpsd_ad_sync);
/*enable lpsd DC offset*/
regmap_update_bits(sunxi_mad->regmap, SUNXI_MAD_LPSD_CH_MASK,
0x1 << MAD_LPSD_DCBLOCK_EN,
0x1 << MAD_LPSD_DCBLOCK_EN);
regmap_write(sunxi_mad->regmap, SUNXI_MAD_LPSD_TH, lpsd_th);
regmap_write(sunxi_mad->regmap, SUNXI_MAD_LPSD_RRUN, lpsd_rrun);
regmap_write(sunxi_mad->regmap, SUNXI_MAD_LPSD_RSTOP, lpsd_rstop);
regmap_write(sunxi_mad->regmap, SUNXI_MAD_LPSD_ECNT, lpsd_ecnt);
}
EXPORT_SYMBOL_GPL(sunxi_mad_lpsd_init);
void sunxi_sram_ahb1_threshole_init(void)
{
int i = 0;
/*config sunxi_mad_ahb1_rx_th_reg*/
for (i = 0; i < ARRAY_SIZE(mad_sram_size); i++) {
if (mad_sram_size[i].src_chan_num ==
sunxi_mad->audio_src_chan_num) {
regmap_write(sunxi_mad->regmap, SUNXI_MAD_SRAM_AHB1_RX_TH,
mad_sram_size[i].ahb1_rx_th);
pr_debug("[%s] MAD_SRAM_AHB1_RX_TH:%dkB\n",
__func__, mad_sram_size[i].ahb1_rx_th);
regmap_write(sunxi_mad->regmap, SUNXI_MAD_SRAM_AHB1_TX_TH,
mad_sram_size[i].ahb1_tx_th);
pr_debug("[%s] MAD_SRAM_AHB1_TX_TH:%dkB\n",
__func__, mad_sram_size[i].ahb1_tx_th);
break;
}
}
}
EXPORT_SYMBOL_GPL(sunxi_sram_ahb1_threshole_init);
void sunxi_mad_sram_set_reset_bit(void)
{
regmap_update_bits(sunxi_mad->regmap, SUNXI_MAD_CTRL,
1 << SRAM_RST, 1 << SRAM_RST);
regmap_update_bits(sunxi_mad->regmap, SUNXI_MAD_CTRL,
1 << SRAM_RST, 0 << SRAM_RST);
}
EXPORT_SYMBOL_GPL(sunxi_mad_sram_set_reset_bit);
int sunxi_mad_sram_set_reset_flag(enum sunxi_mad_sram_reset_flag reset_flag)
{
sunxi_mad->sram_reset_flag = reset_flag;
return 0;
}
EXPORT_SYMBOL_GPL(sunxi_mad_sram_set_reset_flag);
enum sunxi_mad_sram_reset_flag sunxi_mad_sram_get_reset_flag(void)
{
return sunxi_mad->sram_reset_flag;
}
EXPORT_SYMBOL_GPL(sunxi_mad_sram_get_reset_flag);
int sunxi_mad_sram_wait_reset_flag(enum sunxi_mad_sram_reset_flag reset_flag,
unsigned int time_out_msecond)
{
struct timeval old_tv;
struct timeval new_tv;
unsigned int new_msecond = 0;
unsigned int old_msecond = 0;
if (time_out_msecond < 1)
time_out_msecond = 1;
do_gettimeofday(&old_tv);
old_msecond = old_tv.tv_usec/1000 + old_tv.tv_sec * 1000;
while (sunxi_mad->sram_reset_flag != reset_flag) {
do_gettimeofday(&new_tv);
new_msecond = new_tv.tv_usec/1000 + new_tv.tv_sec * 1000;
if (abs(new_msecond - old_msecond) > time_out_msecond)
break;
usleep_range(1000, 5000);
}
return 0;
}
EXPORT_SYMBOL_GPL(sunxi_mad_sram_wait_reset_flag);
void sunxi_mad_sram_init(void)
{
int i = 0;
regmap_write(sunxi_mad->regmap, SUNXI_MAD_SRAM_POINT, 0x00);
for (i = 0; i < ARRAY_SIZE(mad_sram_size); i++) {
if (mad_sram_size[i].src_chan_num ==
sunxi_mad->audio_src_chan_num) {
regmap_write(sunxi_mad->regmap, SUNXI_MAD_SRAM_SIZE,
mad_sram_size[i].size);
pr_debug("[%s] MAD_SRAM_SIZE:%dkB\n",
__func__, mad_sram_size[i].size);
regmap_write(sunxi_mad->regmap, SUNXI_MAD_SRAM_STORE_TH,
mad_sram_size[i].sram_store_th);
pr_debug("[%s] MAD_SRAM_STORE_TH:%dkB\n",
__func__, mad_sram_size[i].sram_store_th);
break;
}
}
/*config sunxi_mad_sram_sec_region_reg, non-sec*/
regmap_write(sunxi_mad->regmap, SUNXI_MAD_SRAM_SEC_REGION_REG, 0x0);
regmap_update_bits(sunxi_mad->regmap, SUNXI_MAD_CTRL,
1 << SRAM_RST, 1 << SRAM_RST);
regmap_update_bits(sunxi_mad->regmap, SUNXI_MAD_CTRL,
1 << SRAM_RST, 0 << SRAM_RST);
sunxi_mad_sram_set_reset_flag(SUNXI_MAD_SRAM_RESET_IDLE);
}
EXPORT_SYMBOL_GPL(sunxi_mad_sram_init);
struct sunxi_mad_info *sunxi_mad_get_mad_info(void)
{
return sunxi_mad;
}
EXPORT_SYMBOL_GPL(sunxi_mad_get_mad_info);
void sunxi_mad_clk_enable(bool val)
{
if (val)
clk_prepare_enable(sunxi_mad->mad_clk);
else
clk_disable_unprepare(sunxi_mad->mad_clk);
}
EXPORT_SYMBOL_GPL(sunxi_mad_clk_enable);
void sunxi_mad_ad_clk_enable(bool val)
{
if (val)
clk_prepare_enable(sunxi_mad->mad_ad_clk);
else
clk_disable_unprepare(sunxi_mad->mad_ad_clk);
}
EXPORT_SYMBOL_GPL(sunxi_mad_ad_clk_enable);
void sunxi_mad_cfg_clk_enable(bool val)
{
if (val)
clk_prepare_enable(sunxi_mad->mad_cfg_clk);
else
clk_disable_unprepare(sunxi_mad->mad_cfg_clk);
}
EXPORT_SYMBOL_GPL(sunxi_mad_cfg_clk_enable);
void sunxi_lpsd_clk_enable(bool val)
{
if (val)
clk_prepare_enable(sunxi_mad->lpsd_clk);
else
clk_disable_unprepare(sunxi_mad->lpsd_clk);
}
EXPORT_SYMBOL_GPL(sunxi_lpsd_clk_enable);
void sunxi_mad_module_clk_enable(bool val)
{
if (val) {
clk_prepare_enable(sunxi_mad->mad_clk);
clk_prepare_enable(sunxi_mad->mad_ad_clk);
clk_prepare_enable(sunxi_mad->mad_cfg_clk);
} else {
clk_disable_unprepare(sunxi_mad->mad_cfg_clk);
clk_disable_unprepare(sunxi_mad->mad_ad_clk);
clk_disable_unprepare(sunxi_mad->mad_clk);
}
}
EXPORT_SYMBOL_GPL(sunxi_mad_module_clk_enable);
void sunxi_mad_standby_chan_sel(unsigned int num)
{
pr_debug("[%s] standby_chan_sel: %d\n", __func__, num);
sunxi_mad->standby_chan_sel = num;
}
EXPORT_SYMBOL_GPL(sunxi_mad_standby_chan_sel);
void sunxi_lpsd_chan_sel(unsigned int num)
{
pr_debug("[%s] lpsd_chan_sel: %d\n", __func__, num);
sunxi_mad->lpsd_chan_sel = num;
}
EXPORT_SYMBOL_GPL(sunxi_lpsd_chan_sel);
/*
* should be called before the sunxi_mad_sram_init.
*/
void sunxi_mad_audio_src_chan_num(unsigned int num)
{
pr_debug("[%s] audio_src_chan_num:%d\n", __func__, num);
sunxi_mad->audio_src_chan_num = num;
}
EXPORT_SYMBOL_GPL(sunxi_mad_audio_src_chan_num);
#if 0
static void sunxi_mad_int_info_show(void)
{
unsigned int val = 0;
regmap_read(sunxi_mad->regmap, SUNXI_MAD_CTRL, &val);
pr_err("[%s] --> SUNXI_MAD_CTRL:0x%x\n", __func__, val);
regmap_read(sunxi_mad->regmap, SUNXI_MAD_SRAM_CH_MASK, &val);
pr_err("[%s] --> SUNXI_MAD_SRAM_CH_MASK:0x%x\n", __func__, val);
regmap_read(sunxi_mad->regmap, SUNXI_MAD_LPSD_CH_MASK, &val);
pr_err("[%s] --> SUNXI_MAD_LPSD_CH_MASK:0x%x\n", __func__, val);
regmap_read(sunxi_mad->regmap, SUNXI_MAD_INT_ST_CLR, &val);
pr_err("[%s] --> SUNXI_MAD_INT_ST_CLR:0x%x\n", __func__, val);
regmap_read(sunxi_mad->regmap, SUNXI_MAD_INT_MASK, &val);
pr_err("[%s] --> SUNXI_MAD_INT_MASK:0x%x\n", __func__, val);
regmap_read(sunxi_mad->regmap, SUNXI_MAD_STA, &val);
pr_err("[%s] SUNXI_MAD_STA:0x%x\n", __func__, val);
if (((val >> MAD_STATE) & 0xF) == 0)
pr_alert("[%s] MAD_STATE: ---> IDLE\n", __func__);
else if ((val >> MAD_STATE) & 0x1)
pr_alert("[%s] MAD_STATE: ---> WAIT\n", __func__);
else if ((val >> (MAD_STATE + 1)) & 0x1)
pr_alert("[%s] MAD_STATE: ---> RUN\n", __func__);
else if ((val >> (MAD_STATE + 2)) & 0x1)
pr_alert("[%s] MAD_STATE: ---> NORMAL\n", __func__);
regmap_read(sunxi_mad->regmap, SUNXI_MAD_DEBUG, &val);
pr_alert("[%s] SUNXI_MAD_DEBUG:0x%x\n", __func__, val);
regmap_write(sunxi_mad->regmap, SUNXI_MAD_DEBUG, 0x7F);
}
#endif
void sunxi_mad_set_lpsdreq(bool enable)
{
regmap_update_bits(sunxi_mad->regmap, SUNXI_MAD_INT_MASK,
0x1 << MAD_REQ_INT_MASK, enable << MAD_REQ_INT_MASK);
}
EXPORT_SYMBOL_GPL(sunxi_mad_set_lpsdreq);
void sunxi_mad_set_datareq(bool enable)
{
regmap_update_bits(sunxi_mad->regmap, SUNXI_MAD_INT_MASK,
0x1 << DATA_REQ_INT_MASK, enable << DATA_REQ_INT_MASK);
}
EXPORT_SYMBOL_GPL(sunxi_mad_set_datareq);
#ifdef SUNXI_MAD_NETLINK_USE
static enum sunxi_mad_netlink_status sunxi_mad_set_netlink_status(
struct sunxi_mad_event *mad_event,
enum sunxi_mad_netlink_status netlink_status)
{
if (!mad_event)
return SNDRV_MAD_NETLINK_NULL;
mad_event->mad_netlink.status = netlink_status;
return mad_event->mad_netlink.status;
}
static enum sunxi_mad_netlink_status sunxi_mad_get_netlink_status(
struct sunxi_mad_event *mad_event)
{
if (!mad_event)
return SNDRV_MAD_NETLINK_NULL;
return mad_event->mad_netlink.status;
}
#endif
void sunxi_lpsd_int_stat_clr(void)
{
unsigned int val = 0;
/* must clear the wake req flag */
regmap_read(sunxi_mad->regmap, SUNXI_MAD_INT_ST_CLR, &val);
if (val & (0x1 << WAKE_INT)) {
val &= ~(0x1 << DATA_REQ_INT);
val |= (0x1 << WAKE_INT);
regmap_write(sunxi_mad->regmap, SUNXI_MAD_INT_ST_CLR, val);
}
}
EXPORT_SYMBOL_GPL(sunxi_lpsd_int_stat_clr);
void sunxi_mad_sram_chan_params(unsigned int mad_channels)
{
unsigned int reg_val = 0;
pr_debug("[%s]: mad_channels:%d\n", __func__, mad_channels);
regmap_read(sunxi_mad->regmap, SUNXI_MAD_SRAM_CH_MASK, &reg_val);
/*config mad sram receive audio channel num*/
reg_val &= ~(0x1f << MAD_SRAM_CH_NUM);
reg_val |= mad_channels << MAD_SRAM_CH_NUM;
/* open mad sram receive channels */
reg_val &= ~(0xffff << MAD_SRAM_CH_MASK);
reg_val |= ((1 << mad_channels) - 1) << MAD_SRAM_CH_MASK;
regmap_write(sunxi_mad->regmap, SUNXI_MAD_SRAM_CH_MASK, reg_val);
}
EXPORT_SYMBOL_GPL(sunxi_mad_sram_chan_params);
static int sunxi_mad_sram_chan_com_config(unsigned int audio_src_chan_num,
unsigned int mad_standby_chan_sel, bool enable)
{
unsigned int chan_ch = 0;
unsigned int mad_sram_chan_num = 0;
unsigned int temp_val = 0;
/*transfer to mad_standby channels*/
switch (mad_standby_chan_sel) {
case 0:
mad_sram_chan_num = audio_src_chan_num;
break;
case 1:
mad_sram_chan_num = 2;
break;
case 2:
mad_sram_chan_num = 4;
break;
default:
mad_sram_chan_num = 2;
break;
}
/* Read data at start */
regmap_read(sunxi_mad->regmap, SUNXI_MAD_SRAM_CH_MASK, &temp_val);
/* mad sram receive channels */
temp_val &= ~(0x1F << MAD_SRAM_CH_NUM);
temp_val &= ~(0xFFFF << MAD_SRAM_CH_MASK);
if (enable) {
temp_val |= audio_src_chan_num << MAD_SRAM_CH_NUM;
temp_val |= ((1 << audio_src_chan_num) - 1) << MAD_SRAM_CH_MASK;
} else {
temp_val |= mad_sram_chan_num << MAD_SRAM_CH_NUM;
temp_val |= ((1 << mad_sram_chan_num) - 1) << MAD_SRAM_CH_MASK;
}
/* config mad_sram channel change */
if (mad_standby_chan_sel == 0) {
chan_ch = MAD_CH_COM_NON;
} else if (mad_standby_chan_sel == 1) {
switch (audio_src_chan_num) {
case 2:
chan_ch = MAD_CH_COM_NON;
break;
case 4:
chan_ch = MAD_CH_COM_2CH_TO_4CH;
break;
case 6:
chan_ch = MAD_CH_COM_2CH_TO_6CH;
break;
case 8:
chan_ch = MAD_CH_COM_2CH_TO_8CH;
break;
default:
pr_err("unsupported mad_sram channels!\n");
return -EINVAL;
}
} else if (mad_standby_chan_sel == 2) {
switch (audio_src_chan_num) {
case 4:
chan_ch = MAD_CH_COM_NON;
break;
case 6:
chan_ch = MAD_CH_COM_4CH_TO_6CH;
break;
case 8:
chan_ch = MAD_CH_COM_4CH_TO_8CH;
break;
default:
pr_err("unsupported mad_sram channels!\n");
return -EINVAL;
}
} else {
pr_err("mad_standby channels isn't set up!\n");
return -EINVAL;
}
temp_val &= ~(0xF << MAD_CH_COM_NUM);
temp_val |= chan_ch << MAD_CH_COM_NUM;
/*
* Enable mad_sram channel CHANGE_EN
* when DMA interpolation process finish, the CHANGE_EN bit will be set
* to 0 automaticallly.
*/
if (enable && (chan_ch != MAD_CH_COM_NON))
temp_val |= 0x1 << MAD_CH_CHANGE_EN;
else
temp_val &= ~(0x1 << MAD_CH_CHANGE_EN);
/* Write the value Once! */
if (chan_ch != MAD_CH_COM_NON)
regmap_write(sunxi_mad->regmap, SUNXI_MAD_SRAM_CH_MASK, temp_val);
#ifdef CONFIG_SUNXI_AUDIO_DEBUG
temp_val = 0;
regmap_read(sunxi_mad->regmap, SUNXI_MAD_SRAM_PRE_DSIZE, &temp_val);
snd_printd("SUNXI_MAD_SRAM_PRE_DSIZE:0x%x\n", temp_val);
#endif
return 0;
}
static void sunxi_mad_lpsd_chan_enable(unsigned int lpsd_chan_sel, bool enable)
{
unsigned int reg_val = 0;
regmap_read(sunxi_mad->regmap, SUNXI_MAD_LPSD_CH_MASK, &reg_val);
reg_val &= ~(0xFFFF << MAD_LPSD_CH_MASK);
reg_val &= ~(0x1 << MAD_LPSD_CH_NUM);
if (enable) {
/*transfer to mad_standby lpsd sel channels*/
reg_val |= 1 << lpsd_chan_sel;
/*config LPSD receive audio channel num: 1 channel*/
reg_val |= 0x1 << MAD_LPSD_CH_NUM;
}
/*
* Tips:
* The lpsd chan num and the channel mask should be setup
* at the same time.
*/
regmap_write(sunxi_mad->regmap, SUNXI_MAD_LPSD_CH_MASK, reg_val);
}
static void sunxi_mad_interrupt_status_clear(struct sunxi_mad_info *sunxi_mad)
{
unsigned int reg_val = 0;
for (;;) {
regmap_read(sunxi_mad->regmap, SUNXI_MAD_INT_ST_CLR, &reg_val);
if (reg_val & (0x1 << DATA_REQ_INT)) {
/* clear data int state */
reg_val &= ~(0x1 << WAKE_INT);
reg_val |= (0x1 << DATA_REQ_INT);
regmap_write(sunxi_mad->regmap, SUNXI_MAD_INT_ST_CLR, reg_val);
} else
break;
}
}
static void sunxi_lpsd_interrupt_status_clear(struct sunxi_mad_info *sunxi_mad)
{
unsigned int reg_val = 0;
for (;;) {
regmap_read(sunxi_mad->regmap, SUNXI_MAD_INT_ST_CLR, &reg_val);
if (reg_val & (0x1 << WAKE_INT)) {
/* clear data int state */
reg_val &= ~(0x1 << DATA_REQ_INT);
reg_val |= (0x1 << WAKE_INT);
regmap_write(sunxi_mad->regmap, SUNXI_MAD_INT_ST_CLR, reg_val);
} else
break;
}
}
static void sunxi_mad_work_resume(struct work_struct *work)
{
struct sunxi_mad_info *sunxi_mad =
container_of(work, struct sunxi_mad_info, ws_resume);
unsigned int break_flag = 0;
u32 reg_val = 0;
spin_lock(&(sunxi_mad->resume_spin));
sunxi_mad->status = SUNXI_MAD_RESUME;
spin_unlock(&(sunxi_mad->resume_spin));
snd_printk("[%s] Start.\n", __func__);
/* disable the wake req */
regmap_update_bits(sunxi_mad->regmap, SUNXI_MAD_INT_MASK,
0x1 << MAD_REQ_INT_MASK, 0x0 << MAD_REQ_INT_MASK);
sunxi_lpsd_interrupt_status_clear(sunxi_mad);
#ifdef SUNXI_MAD_NETLINK_USE
if (sunxi_mad_get_netlink_status(mad_event) == SNDRV_MAD_NETLINK_SUSPEND) {
sunxi_mad_netlink_send(&mad_event, "resume");
sunxi_mad_set_netlink_status(&mad_event, SNDRV_MAD_NETLINK_RESUME);
}
#endif
/* it maybe need 200-800ms */
for (;;) {
/*
regmap_read(sunxi_mad->regmap, SUNXI_MAD_STA, &reg_val);
if (((reg_val & (0x3 << MAD_LPSD_STAT)) == 0) &&
((break_flag & 0x1) == 0x0)) {
sunxi_mad_lpsd_chan_enable(0, false);
break_flag |= 0x1 << 0x0;
}
*/
regmap_read(sunxi_mad->regmap,
SUNXI_MAD_SRAM_CH_MASK, &reg_val);
reg_val = (reg_val >> MAD_CH_CHANGE_EN) & 0x1;
if ((reg_val == 0) && ((break_flag & 0x2) != 0x2))
break_flag |= 0x2;
// if (break_flag == 0x3)
if (break_flag & 0x2)
break;
usleep_range(5000, 10000);
}
#ifdef CONFIG_SUNXI_AUDIO_DEBUG
/* SUNXI_MAD_DMA_TF_SIZE[0x4C] */
regmap_read(sunxi_mad->regmap, SUNXI_MAD_DMA_TF_SIZE, &reg_val);
snd_printk("SUNXI_MAD_DMA_TF_SIZE:0x%x\n", reg_val);
regmap_read(sunxi_mad->regmap, SUNXI_MAD_RD_SIZE, &reg_val);
snd_printk("KEY_WORD_OK: MAD_RD_SIZE[0x%x]:0x%x.\n",
SUNXI_MAD_RD_SIZE, reg_val);
#endif
regmap_update_bits(sunxi_mad->regmap, SUNXI_MAD_CTRL,
0x1 << CPUS_RD_DONE, 0x0 << CPUS_RD_DONE);
#ifndef SUNXI_LPSD_CLK_ALWAYS_ON
sunxi_mad_lpsd_chan_enable(0, false);
sunxi_lpsd_clk_enable(false);
#endif
sunxi_mad_enable(false);
if (sunxi_mad->wakeup_flag & SUNXI_MAD_WAKEUP_LPSD_IRQ)
sunxi_mad->wakeup_flag &= ~SUNXI_MAD_WAKEUP_LPSD_IRQ;
if (sunxi_mad->wakeup_flag & SUNXI_MAD_WAKEUP_MAD_IRQ)
sunxi_mad->wakeup_flag &= ~SUNXI_MAD_WAKEUP_MAD_IRQ;
snd_printk("[%s] Stop.\n", __func__);
}
#ifdef SUNXI_MAD_NETLINK_USE
#if 0
static void sunxi_suspend_delayed(struct work_struct *work)
{
struct sunxi_mad_info *sunxi_mad =
container_of(work, struct sunxi_mad_info, ws_suspend.work);
sunxi_mad_set_netlink_status(&mad_event, SNDRV_MAD_NETLINK_SUSPEND);
}
#endif
#endif
/* not to wakeup to system when time is too small. */
static int sunxi_mad_suspend_update_time(struct sunxi_mad_info *sunxi_mad)
{
if (sunxi_mad == NULL) {
pr_alert("[%s] sunxi_mad is NULL!!!\n", __func__);
return -EINVAL;
}
do_gettimeofday(&(sunxi_mad->suspend_tv));
return 0;
}
#if 0
static int sunxi_mad_suspend_get_time(struct sunxi_mad_info *sunxi_mad,
struct timeval *time_val)
{
if (sunxi_mad == NULL) {
pr_alert("[%s] sunxi_mad is NULL!!!\n", __func__);
return -EINVAL;
}
memcpy(time_val, &(sunxi_mad->suspend_tv), sizeof(struct timeval));
return 0;
}
static bool sunxi_mad_lpsd_check_delay(struct sunxi_mad_info *sunxi_mad,
unsigned int time_msecond)
{
struct timeval old_tv;
struct timeval new_tv;
unsigned int new_msecond = 0;
unsigned int old_msecond = 0;
do_gettimeofday(&new_tv);
sunxi_mad_suspend_get_time(sunxi_mad, &old_tv);
if (time_msecond < 1)
time_msecond = 1;
new_msecond = new_tv.tv_usec/1000 + new_tv.tv_sec * 1000;
old_msecond = old_tv.tv_usec/1000 + old_tv.tv_sec * 1000;
if (abs(new_msecond - old_msecond) > time_msecond)
return true;
else
return false;
}
#endif
static irqreturn_t sunxi_lpsd_interrupt(int irq, void *dev_id)
{
struct sunxi_mad_info *sunxi_mad = dev_id;
pr_debug("[%s] Start.\n", __func__);
regmap_read(sunxi_mad->regmap, SUNXI_MAD_SRAM_RD_POINT,
&(sunxi_mad->sram_rd_point));
sunxi_mad->wakeup_flag |= SUNXI_MAD_WAKEUP_LPSD_IRQ;
/* disable the wake req */
regmap_update_bits(sunxi_mad->regmap, SUNXI_MAD_INT_MASK,
0x1 << MAD_REQ_INT_MASK, 0x0 << MAD_REQ_INT_MASK);
sunxi_lpsd_interrupt_status_clear(sunxi_mad);
/* FIXME:not use for wakeup */
if (!(sunxi_mad->wakeup_flag & SUNXI_MAD_WAKEUP_USE)) {
sunxi_lpsd_interrupt_status_clear(sunxi_mad);
pr_alert("[%s] SUNXI_MAD_WAKEUP_USE is off.\n", __func__);
return IRQ_HANDLED;
}
if (sunxi_mad->status != SUNXI_MAD_SUSPEND) {
sunxi_lpsd_interrupt_status_clear(sunxi_mad);
pr_alert("[%s] device was not be suspended!\n", __func__);
return IRQ_HANDLED;
}
if (device_may_wakeup(sunxi_mad->dev)) {
/*
* FIXME: wakeup interrupt when suspending.
* the app should setup cmd example:
* cat /sys/power/wakeup_count
* echo xxx > /sys/power/wakeup_count
*/
pm_stay_awake(sunxi_mad->dev);
pm_wakeup_event(sunxi_mad->dev, 0);
pm_relax(sunxi_mad->dev);
pr_warn("[%s] SRAM_RD_POINT:0x%x.\n", __func__,
sunxi_mad->sram_rd_point);
} else {
sunxi_lpsd_interrupt_status_clear(sunxi_mad);
pr_warn("[%s] device was not wakeup.\n", __func__);
}
regmap_update_bits(sunxi_mad->regmap, SUNXI_MAD_CTRL,
0x1 << KEY_WORD_OK, 0x1 << KEY_WORD_OK);
if (sunxi_mad->suspend_flag & SUNXI_MAD_STANDBY_SRAM_MEM)
sunxi_mad_dma_type(SUNXI_MAD_DMA_IO);
sunxi_mad_sram_chan_com_config(sunxi_mad->audio_src_chan_num,
sunxi_mad->standby_chan_sel, true);
#ifdef SUNXI_MAD_DATA_INT_USE
/* enable the data req and wake req */
regmap_update_bits(sunxi_mad->regmap, SUNXI_MAD_INT_MASK,
0x1 << DATA_REQ_INT_MASK, 0x1 << DATA_REQ_INT_MASK);
#else
schedule_work(&(sunxi_mad->ws_resume));
#endif
pr_debug("[%s] Stop.\n", __func__);
return IRQ_HANDLED;
}
static irqreturn_t sunxi_mad_interrupt(int irq, void *dev_id)
{
struct sunxi_mad_info *sunxi_mad = dev_id;
unsigned int val = 0;
snd_printd("[%s] Start.\n", __func__);
sunxi_mad->wakeup_flag |= SUNXI_MAD_WAKEUP_MAD_IRQ;
regmap_read(sunxi_mad->regmap, SUNXI_MAD_INT_ST_CLR, &val);
if (val & (0x1 << DATA_REQ_INT)) {
/* disable the data req and wake req */
regmap_update_bits(sunxi_mad->regmap, SUNXI_MAD_INT_MASK,
0x1 << DATA_REQ_INT_MASK, 0x0 << DATA_REQ_INT_MASK);
schedule_work(&(sunxi_mad->ws_resume));
/* clear data int state */
sunxi_mad_interrupt_status_clear(sunxi_mad);
}
snd_printd("[%s] Stop.\n", __func__);
return IRQ_HANDLED;
}
void sunxi_sram_dma_config(struct sunxi_dma_params *capture_dma_param)
{
capture_dma_param->dma_addr = MAD_SRAM_DMA_SRC_ADDR;
capture_dma_param->dma_drq_type_num = DRQSRC_MAD_RX;
}
EXPORT_SYMBOL_GPL(sunxi_sram_dma_config);
void sunxi_mad_dma_type(enum sunxi_mad_dma_type dma_type)
{
/* config sunxi_mad_sram dma type should be before DMA_EN */
switch (dma_type) {
case SUNXI_MAD_DMA_MEM:
default:
regmap_update_bits(sunxi_mad->regmap, SUNXI_MAD_CTRL,
0x1 << DMA_TYPE, 0x0 << DMA_TYPE);
break;
case SUNXI_MAD_DMA_IO:
regmap_update_bits(sunxi_mad->regmap, SUNXI_MAD_CTRL,
0x1 << DMA_TYPE, 0x1 << DMA_TYPE);
break;
}
}
EXPORT_SYMBOL_GPL(sunxi_mad_dma_type);
void sunxi_mad_dma_enable(bool enable)
{
regmap_update_bits(sunxi_mad->regmap, SUNXI_MAD_CTRL,
0x1 << DMA_EN, enable << DMA_EN);
}
EXPORT_SYMBOL_GPL(sunxi_mad_dma_enable);
int sunxi_mad_open(void)
{
sunxi_mad->status = SUNXI_MAD_OPEN;
return 0;
}
EXPORT_SYMBOL_GPL(sunxi_mad_open);
int sunxi_mad_enable(bool enable)
{
u32 reg_val = 0;
unsigned int mad_is_worked = 1; /*not work*/
/*open MAD_EN*/
regmap_update_bits(sunxi_mad->regmap, SUNXI_MAD_CTRL,
0x1 << MAD_EN, enable << MAD_EN);
if (enable) {
/* if mad is working well */
regmap_read(sunxi_mad->regmap, SUNXI_MAD_STA, &reg_val);
reg_val |= ~(0x1 << MAD_RUN);
mad_is_worked = ~reg_val;
if (mad_is_worked) {
pr_alert("mad isn't working right!\n");
return -EBUSY;
}
}
return 0;
}
EXPORT_SYMBOL_GPL(sunxi_mad_enable);
void sunxi_mad_set_go_on_sleep(bool enable)
{
regmap_update_bits(sunxi_mad->regmap, SUNXI_MAD_CTRL,
0x1 << GO_ON_SLEEP, enable << GO_ON_SLEEP);
}
EXPORT_SYMBOL_GPL(sunxi_mad_set_go_on_sleep);
int sunxi_mad_close(void)
{
sunxi_mad_enable(false);
/*
* when sram used for optee at suspend,
* mad should set the io type to memcpy.
*/
sunxi_mad_dma_type(SUNXI_MAD_DMA_MEM);
sunxi_mad->audio_src_path = 0;
sunxi_mad->audio_src_chan_num = 0;
sunxi_mad->standby_chan_sel = 0;
sunxi_mad->lpsd_chan_sel = 0;
sunxi_mad->status = SUNXI_MAD_CLOSE;
return 0;
}
EXPORT_SYMBOL_GPL(sunxi_mad_close);
int sunxi_mad_hw_params(unsigned int mad_channels, unsigned int sample_rate)
{
snd_printd("[%s] mad_channels: %d, sample_rate:%d\n", __func__,
mad_channels, sample_rate);
/*config mad sram audio source channel num*/
regmap_update_bits(sunxi_mad->regmap, SUNXI_MAD_SRAM_CH_MASK,
0x1f << MAD_AD_SRC_CH_NUM, mad_channels << MAD_AD_SRC_CH_NUM);
sunxi_mad_sram_chan_params(mad_channels);
/* keep lpsd running in 16kHz */
if (sample_rate == 16000)
regmap_update_bits(sunxi_mad->regmap, SUNXI_MAD_CTRL,
0x1 << AUDIO_DATA_SYNC_FRC, 0x0 << AUDIO_DATA_SYNC_FRC);
else if (sample_rate == 48000)
regmap_update_bits(sunxi_mad->regmap, SUNXI_MAD_CTRL,
0x1 << AUDIO_DATA_SYNC_FRC, 0x1 << AUDIO_DATA_SYNC_FRC);
else
return -EINVAL;
sunxi_mad->status = SUNXI_MAD_PARAMS;
return 0;
}
EXPORT_SYMBOL_GPL(sunxi_mad_hw_params);
int sunxi_mad_audio_source_sel(unsigned int path_sel, unsigned int enable)
{
char *path_str = NULL;
switch (path_sel) {
case 0:
path_str = "No-Audio";
break;
case 1:
path_str = "I2S0-Input";
break;
case 2:
path_str = "Codec-Input";
break;
case 3:
path_str = "DMIC-Input";
break;
case 4:
path_str = "I2S1-Input";
break;
case 5:
path_str = "I2S2-Input";
break;
default:
path_str = "Error-Input";
return -EINVAL;
break;
}
pr_warn("[%s] %s\n", __func__, path_str);
if (enable) {
if ((path_sel >= 1) && (path_sel <= 5)) {
regmap_update_bits(sunxi_mad->regmap,
SUNXI_MAD_AD_PATH_SEL,
MAD_AD_PATH_SEL_MASK,
path_sel << MAD_AD_PATH_SEL);
sunxi_mad->audio_src_path = path_sel;
} else
return -EINVAL;
} else {
regmap_update_bits(sunxi_mad->regmap, SUNXI_MAD_AD_PATH_SEL,
MAD_AD_PATH_SEL_MASK, 0 << MAD_AD_PATH_SEL);
}
return 0;
}
EXPORT_SYMBOL_GPL(sunxi_mad_audio_source_sel);
#ifdef CONFIG_SUNXI_AUDIO_DEBUG
static void sunx_mad_show_all_regs(struct sunxi_mad_info *sunxi_mad)
{
unsigned int reg_val[4] = {0};
int reg_offset = 0;
for (reg_offset = 0; reg_offset < 0x6c; reg_offset += 0x10) {
regmap_read(sunxi_mad->regmap, reg_offset + 0x0, &reg_val[0]);
regmap_read(sunxi_mad->regmap, reg_offset + 0x4, &reg_val[1]);
regmap_read(sunxi_mad->regmap, reg_offset + 0x8, &reg_val[2]);
regmap_read(sunxi_mad->regmap, reg_offset + 0xc, &reg_val[3]);
pr_warn("[%s] 0x%x-0x%x:\t 0x%-8x\t 0x%-8x\t 0x%-8x\t 0x%-8x\n",
__func__, reg_offset, reg_offset+0xc,
reg_val[0], reg_val[1], reg_val[2], reg_val[3]);
}
}
#endif
/*for ASOC cpu_dai*/
int sunxi_mad_suspend_external(void)
{
if (sunxi_mad->status == SUNXI_MAD_SUSPEND) {
pr_warn("[%s] sunxi mad has suspend!\n", __func__);
return 0;
}
regmap_update_bits(sunxi_mad->regmap, SUNXI_MAD_CTRL,
0x1 << DMA_EN, 0x0 << DMA_EN);
/*
* config sunxi_mad_sram as memory
* eg: because optee need use the sram.
* the sram should stop to data.
*/
if (sunxi_mad->suspend_flag & SUNXI_MAD_STANDBY_SRAM_MEM) {
sunxi_mad_dma_type(SUNXI_MAD_DMA_MEM);
sunxi_mad_sram_chan_params(0);
} else
sunxi_mad_sram_chan_com_config(sunxi_mad->audio_src_chan_num,
sunxi_mad->standby_chan_sel, false);
#ifndef SUNXI_LPSD_CLK_ALWAYS_ON
sunxi_lpsd_clk_enable(true);
#endif
sunxi_lpsd_interrupt_status_clear(sunxi_mad);
sunxi_mad_lpsd_init();
sunxi_mad_lpsd_chan_enable(sunxi_mad->lpsd_chan_sel, true);
sunxi_lpsd_interrupt_status_clear(sunxi_mad);
/* disable the data req and enable wake req */
regmap_update_bits(sunxi_mad->regmap, SUNXI_MAD_INT_MASK,
0x1 << DATA_REQ_INT_MASK, 0x0 << DATA_REQ_INT_MASK);
regmap_update_bits(sunxi_mad->regmap, SUNXI_MAD_INT_MASK,
0x1 << MAD_REQ_INT_MASK, 0x1 << MAD_REQ_INT_MASK);
sunxi_mad_enable(true);
sunxi_mad_set_go_on_sleep(true);
sunxi_mad->status = SUNXI_MAD_SUSPEND;
#ifdef SUNXI_MAD_NETLINK_USE
#ifdef SUNXI_MAD_NETLINK_APP_PAUSE_USE
/* need to notify app the suspend status. */
sunxi_mad_netlink_send(&mad_event, "suspend");
#endif
sunxi_mad_set_netlink_status(&mad_event, SNDRV_MAD_NETLINK_SUSPEND);
#endif
sunxi_mad_suspend_update_time(sunxi_mad);
#ifdef CONFIG_SUNXI_AUDIO_DEBUG
sunx_mad_show_all_regs(sunxi_mad);
#endif
snd_printk("[%s] sunxi mad is working right!\n", __func__);
return 0;
}
EXPORT_SYMBOL_GPL(sunxi_mad_suspend_external);
/*for ASOC cpu_dai*/
int sunxi_mad_resume_external(void)
{
#ifdef CONFIG_SUNXI_AUDIO_DEBUG
unsigned int reg_val = 0;
#endif
snd_printd("[%s] Start.\n", __func__);
/* disable the wake req */
regmap_update_bits(sunxi_mad->regmap, SUNXI_MAD_INT_MASK,
0x1 << MAD_REQ_INT_MASK, 0x0 << MAD_REQ_INT_MASK);
if ((sunxi_mad->wakeup_flag & SUNXI_MAD_WAKEUP_LPSD_IRQ) ||
(sunxi_mad->wakeup_flag & SUNXI_MAD_WAKEUP_MAD_IRQ)) {
pr_alert("[%s] sunxi mad has wakeup irq!\n", __func__);
return 0;
}
spin_lock(&(sunxi_mad->resume_spin));
if (sunxi_mad->status == SUNXI_MAD_RESUME) {
pr_warn("[%s] sunxi mad has resume!\n", __func__);
spin_unlock(&(sunxi_mad->resume_spin));
return 0;
}
sunxi_mad->status = SUNXI_MAD_RESUME;
spin_unlock(&(sunxi_mad->resume_spin));
/* not a lpsd interrupt resume */
if (sunxi_mad->suspend_flag & SUNXI_MAD_STANDBY_SRAM_MEM)
sunxi_mad_dma_type(SUNXI_MAD_DMA_IO);
sunxi_mad_sram_chan_com_config(sunxi_mad->audio_src_chan_num,
sunxi_mad->standby_chan_sel, true);
schedule_work(&(sunxi_mad->ws_resume));;
snd_printd("[%s] Stop.\n", __func__);
return 0;
}
EXPORT_SYMBOL_GPL(sunxi_mad_resume_external);
static void sunxi_mad_sram_set_bmode(bool mode)
{
unsigned int reg = 0;
void __iomem *sram_bmode_ctrl_reg = ioremap(SRAM_BMODE_CTRL_REG, 4);
reg = readl(sram_bmode_ctrl_reg);
if (mode)
reg |= (0x1 << MAD_SRAM_BMODE_CTRL);
else
reg &= ~(0x1 << MAD_SRAM_BMODE_CTRL);
writel(reg, sram_bmode_ctrl_reg);
iounmap(sram_bmode_ctrl_reg);
}
/*for internal use*/
static int sunxi_mad_suspend(struct device *dev)
{
/*
* the standb system driver will set it to BOOT Mode
* befor system suspended if the ddr will be setup refresh.
*/
//sunxi_mad_sram_set_bmode(MAD_SRAM_BMODE_BOOT);
return 0;
}
/*for internal use*/
static int sunxi_mad_resume(struct device *dev)
{
sunxi_mad_sram_set_bmode(MAD_SRAM_BMODE_NORMAL);
return 0;
}
static const struct regmap_config sunxi_mad_regmap_config = {
.reg_bits = 32,
.reg_stride = 4,
.val_bits = 32,
.max_register = SUNXI_MAD_DEBUG,
.cache_type = REGCACHE_NONE,
};
#ifdef SUNXI_MAD_NETLINK_USE
static int mad_netlink_send(struct sunxi_mad_event *mad_event,
struct sock *sock, int group,
u16 type, void *msg, int len)
{
struct sk_buff *skb = NULL;
struct nlmsghdr *nlh;
int ret = 0;
skb = nlmsg_new(len, GFP_ATOMIC);
if (!skb) {
kfree_skb(skb);
return -EMSGSIZE;
}
nlh = nlmsg_put(skb, 0, 0, type, len, 0);
if (!nlh) {
kfree_skb(skb);
return -EMSGSIZE;
}
memcpy(nlmsg_data(nlh), msg, len);
NETLINK_CB(skb).portid = 0;
NETLINK_CB(skb).dst_group = 0;
if (sunxi_mad_get_netlink_status(mad_event) > SNDRV_MAD_NETLINK_CLOSE) {
ret = netlink_unicast(sock, skb, mad_event->mad_netlink.pid, GFP_ATOMIC);
if (ret < 0)
sunxi_mad_set_netlink_status(mad_event, SNDRV_MAD_NETLINK_CLOSE);
}
return ret;
}
static void mad_netlink_rcv(struct sk_buff *skb)
{
struct nlmsghdr *nlh;
int pid;
nlh = (struct nlmsghdr *)skb->data;
pid = nlh->nlmsg_pid; /*pid of sending process */
/* user start capture? */
if (!strncmp(nlmsg_data(nlh), "start", 5)) {
mad_event.mad_netlink.pid = pid;
sunxi_mad_set_netlink_status(&mad_event, SNDRV_MAD_NETLINK_START);
}
#ifdef SUNXI_MAD_NETLINK_APP_PAUSE_USE
if (!strncmp(nlmsg_data(nlh), "pause", 5)) {
sunxi_mad_set_netlink_status(&mad_event, SNDRV_MAD_NETLINK_PAUSE);
/* for app setup the mad working!!!!!!*/
sunxi_mad_suspend_external();
}
#endif
#ifdef SUNXI_MAD_NETLINK_APP_RESUME_USE
if (!strncmp(nlmsg_data(nlh), "resume", 6))
sunxi_mad_set_netlink_status(&mad_event, SNDRV_MAD_NETLINK_RESUME);
#endif
if (!strncmp(nlmsg_data(nlh), "close", 5))
sunxi_mad_set_netlink_status(&mad_event, SNDRV_MAD_NETLINK_CLOSE);
}
void sunxi_mad_netlink_send(struct sunxi_mad_event *mad_event,
const char *fmt, ...)
{
va_list args;
char evt_data[EVT_MAX_SIZE];
int size;
va_start(args, fmt);
size = vscnprintf(evt_data, EVT_MAX_SIZE, fmt, args);
/* mark and strip a trailing newline */
if (size && evt_data[size - 1] == '\n')
size--;
va_end(args);
mad_netlink_send(mad_event, mad_event->sock, 0, 0, evt_data, size);
}
#endif
/*
* Connector properties
*/
static ssize_t mad_standby_sram_type_store(struct device *device,
struct device_attribute *attr,
const char *buf, size_t count)
{
int ret = 0;
int new_val = 0;
ret = sscanf(buf, "%d", &new_val);
pr_warn("\nret:%d, new_val:%d, mad standby SRAM IO Mode: %s\n", ret,
new_val, sunxi_mad->standby_sram_type?"Memory mode":"IO mode");
if (!(new_val == 0 || new_val == 1)) {
pr_err("\nOnly 1 or 0 can be support.\n");
pr_err("0: for memory type.\n");
pr_err("1: for IO type.\n");
return count;
}
if (new_val == sunxi_mad->standby_sram_type) {
pr_err("new status no change.\n");
return count;
}
sunxi_mad->standby_sram_type = new_val;
if (new_val)
sunxi_mad->suspend_flag |= SUNXI_MAD_STANDBY_SRAM_MEM;
else
sunxi_mad->suspend_flag &= ~SUNXI_MAD_STANDBY_SRAM_MEM;
return count;
}
static ssize_t mad_standby_sram_type_show(struct device *device,
struct device_attribute *attr, char *buf)
{
return snprintf(buf, PAGE_SIZE, "%d\n", sunxi_mad->standby_sram_type);
}
DEVICE_ATTR(standby_sram_mem, 0644,
mad_standby_sram_type_show, mad_standby_sram_type_store);
static ssize_t wakeup_irq_switch_store(struct device *device,
struct device_attribute *attr,
const char *buf, size_t count)
{
int ret = 0;
int new_val = 0;
ret = sscanf(buf, "%d", &new_val);
pr_warn("\nret:%d, new_val:%d, wakeup_irq_en: %d\n",
ret, new_val, sunxi_mad->wakeup_irq_en);
if (!(new_val == 0 || new_val == 1)) {
pr_err("\nOnly 1 or 0 can be support.\n");
return count;
}
if (new_val == sunxi_mad->wakeup_irq_en) {
pr_err("new status no change.\n");
return count;
}
if (!(sunxi_mad->wakeup_flag & SUNXI_MAD_WAKEUP_ON)) {
pr_err("wakeup-source not be setup at dts file!");
return count;
}
sunxi_mad->wakeup_irq_en = new_val;
if (new_val) {
if (sunxi_mad->wakeup_flag & SUNXI_MAD_WAKEUP_USE) {
pr_err("has setup the wakeup irq for wakeup source!");
return count;
}
ret = dev_pm_set_wake_irq(device, sunxi_mad->lpsd_irq);
if (ret < 0) {
dev_err(device, "failed to setup sunxi_mad dev wakeup irq.\n");
return count;
}
sunxi_mad->wakeup_flag |= SUNXI_MAD_WAKEUP_USE;
} else {
if (sunxi_mad->wakeup_flag & SUNXI_MAD_WAKEUP_USE) {
dev_pm_clear_wake_irq(device);
sunxi_mad->wakeup_flag &= ~SUNXI_MAD_WAKEUP_USE;
}
}
return count;
}
static ssize_t wakeup_irq_switch_show(struct device *device,
struct device_attribute *attr,
char *buf)
{
return snprintf(buf, PAGE_SIZE, "%d\n", sunxi_mad->wakeup_irq_en);
}
DEVICE_ATTR(wakeup_en, 0644, wakeup_irq_switch_show, wakeup_irq_switch_store);
static ssize_t sunxi_mad_lpsd_status_store(struct device *device,
struct device_attribute *attr,
const char *buf, size_t count)
{
return count;
}
static ssize_t sunxi_mad_lpsd_status_show(struct device *device,
struct device_attribute *attr, char *buf)
{
unsigned int lpsd_status = 0;
unsigned int reg_val = 0;
regmap_read(sunxi_mad->regmap, SUNXI_MAD_STA, &reg_val);
lpsd_status = (reg_val >> MAD_LPSD_STAT) & 0x3;
return snprintf(buf, PAGE_SIZE, "%u\n", lpsd_status);
}
DEVICE_ATTR(lpsd_status, 0644, sunxi_mad_lpsd_status_show,
sunxi_mad_lpsd_status_store);
static struct attribute *audio_mad_debug_attrs[] = {
&dev_attr_standby_sram_mem.attr,
&dev_attr_wakeup_en.attr,
&dev_attr_lpsd_status.attr,
NULL,
};
static struct attribute_group audio_mad_debug_attr_group = {
.name = "sunxi_mad_audio",
.attrs = audio_mad_debug_attrs,
};
#ifdef SUNXI_MAD_NETLINK_USE
int sunxi_mad_netlink_init(void)
{
struct netlink_kernel_cfg cfg = {
.input = mad_netlink_rcv,
};
mad_event.sock = netlink_kernel_create(&init_net,
SUNXI_NETLINK_MAD, &cfg);
if (!mad_event.sock) {
pr_err("[%s] netlink create failed.\n", __func__);
return -EMSGSIZE;
}
sunxi_mad_set_netlink_status(&mad_event, SNDRV_MAD_NETLINK_CLOSE);
if (mad_event.sock) {
pr_warn("[%s] Creating MAD netlink successfully.\n", __func__);
return 0;
}
pr_err("Creating MAD netlink is failed\n");
return -EBUSY;
}
static void sunxi_mad_netlink_exit(void)
{
if (mad_event.sock) {
netlink_kernel_release(mad_event.sock);
mad_event.sock = NULL;
}
}
#endif
static int sunxi_mad_probe(struct platform_device *pdev)
{
struct resource res, *memregion;
struct device_node *np = pdev->dev.of_node;
unsigned int temp_val;
int ret;
sunxi_mad = devm_kzalloc(&pdev->dev, sizeof(struct sunxi_mad_info),
GFP_KERNEL);
if (!sunxi_mad) {
ret = -ENOMEM;
goto err_node_put;
}
dev_set_drvdata(&pdev->dev, sunxi_mad);
sunxi_mad->dev = &pdev->dev;
ret = of_address_to_resource(np, 0, &res);
if (ret) {
dev_err(&pdev->dev, "Failed to get sunxi mad resource\n");
return -EINVAL;
goto err_devm_kfree;
}
memregion = devm_request_mem_region(&pdev->dev, res.start,
resource_size(&res), DRV_NAME);
if (!memregion) {
dev_err(&pdev->dev, "sunxi mad memory region already claimed\n");
ret = -EBUSY;
goto err_devm_kfree;
}
sunxi_mad->membase = devm_ioremap(&pdev->dev,
res.start, resource_size(&res));
if (!sunxi_mad->membase) {
dev_err(&pdev->dev, "sunxi mad ioremap failed\n");
ret = -EBUSY;
goto err_devm_kfree;
}
sunxi_mad->regmap = devm_regmap_init_mmio(&pdev->dev,
sunxi_mad->membase, &sunxi_mad_regmap_config);
if (IS_ERR_OR_NULL(sunxi_mad->regmap)) {
dev_err(&pdev->dev, "sunxi mad registers regmap failed\n");
ret = -ENOMEM;
goto err_iounmap;
}
sunxi_mad_sram_set_bmode(MAD_SRAM_BMODE_NORMAL);
ret = of_property_read_u32(np, "lpsd_clk_src_cfg", &temp_val);
if (ret < 0) {
pr_debug("Default LPSD clk source is 24 MHz hosc!\n");
sunxi_mad->pll_audio_src_used = 0;
sunxi_mad->hosc_src_used = 1;
} else if (temp_val == 0) {
pr_debug("lpsd clk source is pll_audio, 48 div!\n");
sunxi_mad->pll_audio_src_used = 1;
sunxi_mad->hosc_src_used = 0;
} else if (temp_val == 1) {
pr_debug("sunxi lpsd clk source is 24 MHz hosc!\n");
sunxi_mad->pll_audio_src_used = 0;
sunxi_mad->hosc_src_used = 1;
}
if (sunxi_mad->pll_audio_src_used == 1) {
sunxi_mad->pll_clk = of_clk_get(np, 0);
if (IS_ERR(sunxi_mad->pll_clk)) {
dev_err(&pdev->dev, "Can't get pll clocks\n");
ret = PTR_ERR(sunxi_mad->pll_clk);
goto err_iounmap;
}
} else if (sunxi_mad->hosc_src_used == 1) {
sunxi_mad->hosc_clk = of_clk_get(np, 1);
if (IS_ERR(sunxi_mad->hosc_clk)) {
dev_err(&pdev->dev, "Can't get 24MHz hosc clocks\n");
ret = PTR_ERR(sunxi_mad->hosc_clk);
goto err_iounmap;
}
} else {
dev_err(&pdev->dev, "Can't set parent of lpsd_clk.\n");
ret = -EBUSY;
goto err_iounmap;
}
sunxi_mad->lpsd_clk = of_clk_get(np, 2);
if (IS_ERR(sunxi_mad->lpsd_clk)) {
dev_err(&pdev->dev, "Can't get lpsd clocks\n");
ret = PTR_ERR(sunxi_mad->lpsd_clk);
goto err_lpsd_src_clk_put;
} else if (sunxi_mad->pll_audio_src_used == 1) {
if (clk_set_parent(sunxi_mad->lpsd_clk, sunxi_mad->pll_clk)) {
dev_err(&pdev->dev, "set parent of lpsd_clk to pll_clk fail\n");
ret = -EBUSY;
goto err_lpsd_clk_put;
} else if (clk_prepare_enable(sunxi_mad->pll_clk)) {
dev_err(&pdev->dev, "Can't prepare enable pll_clk\n");
goto err_lpsd_clk_put;
}
} else if (sunxi_mad->hosc_src_used == 1) {
if (clk_set_parent(sunxi_mad->lpsd_clk, sunxi_mad->hosc_clk)) {
dev_err(&pdev->dev, "set parent of lpsd_clk to hosc_clk fail\n");
ret = -EBUSY;
goto err_lpsd_clk_put;
} else if (clk_prepare_enable(sunxi_mad->hosc_clk)) {
dev_err(&pdev->dev, "Can't prepare enable hosc_clk\n");
goto err_lpsd_clk_put;
}
}
#ifdef SUNXI_LPSD_CLK_ALWAYS_ON
if (clk_prepare_enable(sunxi_mad->lpsd_clk)) {
dev_err(&pdev->dev, "Can't prepare enable lpsd_clk\n");
ret = -EBUSY;
goto err_lpsd_src_clk_disable;
}
#endif
sunxi_mad->mad_clk = of_clk_get(np, 3);
if (IS_ERR(sunxi_mad->mad_clk)) {
dev_err(&pdev->dev, "Can't get mad clocks\n");
ret = PTR_ERR(sunxi_mad->mad_clk);
goto err_lpsd_clk_disable;
}
#ifdef MAD_CLK_ALWAYS_ON
if (clk_prepare_enable(sunxi_mad->mad_clk)) {
dev_err(&pdev->dev, "Can't prepare enable mad_clk\n");
goto err_mad_clk_put;
}
#endif
sunxi_mad->mad_ad_clk = of_clk_get(np, 4);
if (IS_ERR(sunxi_mad->mad_ad_clk)) {
dev_err(&pdev->dev, "Can't get mad ad clocks\n");
ret = PTR_ERR(sunxi_mad->mad_ad_clk);
goto err_mad_clk_disable;
}
#ifdef MAD_CLK_ALWAYS_ON
if (clk_prepare_enable(sunxi_mad->mad_ad_clk)) {
dev_err(&pdev->dev, "Can't prepare enable mad_ad_clk\n");
goto err_mad_ad_clk_put;
}
#endif
sunxi_mad->mad_cfg_clk = of_clk_get(np, 5);
if (IS_ERR(sunxi_mad->mad_cfg_clk)) {
dev_err(&pdev->dev, "Can't get mad cfg clocks\n");
ret = PTR_ERR(sunxi_mad->mad_cfg_clk);
goto err_mad_ad_clk_disable;
}
#ifdef MAD_CLK_ALWAYS_ON
if (clk_prepare_enable(sunxi_mad->mad_cfg_clk)) {
dev_err(&pdev->dev, "Can't prepare enable mad_cfg_clk\n");
goto err_mad_cfg_clk_put;
}
#endif
sunxi_mad->lpsd_irq = platform_get_irq(pdev, 0);
if (sunxi_mad->lpsd_irq < 0) {
dev_err(&pdev->dev, "Can't get lpsd irq.\n");
goto err_mad_cfg_clk_disable;
}
/* IRQF_NO_SUSPEND */
ret = devm_request_irq(sunxi_mad->dev, sunxi_mad->lpsd_irq,
sunxi_lpsd_interrupt, 0, "lpsd-irq", sunxi_mad);
if (ret < 0) {
dev_err(&pdev->dev, "lpsd irq request failed.\n");
goto err_mad_cfg_clk_disable;
}
if (of_get_property(np, "wakeup-source", NULL)) {
ret = device_init_wakeup(sunxi_mad->dev, true);
if (ret < 0) {
dev_err(&pdev->dev, "Failed to init sunxi_mad dev wakeup.\n");
goto err_lpsd_irq_free;
}
if (!device_can_wakeup(sunxi_mad->dev)) {
dev_err(&pdev->dev, "it's not a wakup device.\n");
goto err_dev_init_wakeup;
}
sunxi_mad->wakeup_flag |= SUNXI_MAD_WAKEUP_ON;
ret = dev_pm_set_wake_irq(sunxi_mad->dev, sunxi_mad->lpsd_irq);
if (ret < 0) {
dev_err(&pdev->dev, "failed to setup sunxi_mad dev wakeup irq.\n");
goto err_dev_init_wakeup;
}
sunxi_mad->wakeup_irq_en = 1;
sunxi_mad->wakeup_flag |= SUNXI_MAD_WAKEUP_USE;
} else {
dev_warn(&pdev->dev, "can't find sunxi_mad dev wakeup-source.\n");
sunxi_mad->wakeup_irq_en = 0;
sunxi_mad->wakeup_flag &= ~SUNXI_MAD_WAKEUP_ON;
}
sunxi_mad->mad_irq = platform_get_irq(pdev, 1);
if (sunxi_mad->mad_irq < 0) {
pr_err("[audio] sunxi mad data get irq failed!\n");
goto err_dev_wakeup_irq;
}
ret = devm_request_irq(sunxi_mad->dev, sunxi_mad->mad_irq,
sunxi_mad_interrupt, 0, "mad-irq", sunxi_mad);
if (ret < 0) {
pr_err("[audio] sunxi mad data irq request failed!\n");
goto err_dev_wakeup_irq;
}
#ifdef SUNXI_MAD_NETLINK_USE
ret = sunxi_mad_netlink_init();
if (ret < 0) {
pr_err("[audio] sunxi mad data irq request failed!\n");
goto err_mad_irq_free;
}
#endif
ret = of_property_read_u32(np, "standby_sram_io_type", &temp_val);
if (ret < 0) {
pr_alert("Default sram io type is memory\n");
sunxi_mad->standby_sram_type = 1;
sunxi_mad->suspend_flag |= SUNXI_MAD_STANDBY_SRAM_MEM;
} else if (temp_val == 1) {
sunxi_mad->suspend_flag &= ~SUNXI_MAD_STANDBY_SRAM_MEM;
sunxi_mad->standby_sram_type = 0;
pr_warn("SRAM IO type is IO when system standby.\n");
} else {
sunxi_mad->suspend_flag |= SUNXI_MAD_STANDBY_SRAM_MEM;
sunxi_mad->standby_sram_type = 1;
pr_warn("SRAM IO type is memory when system standby.\n");
}
INIT_WORK(&(sunxi_mad->ws_resume), sunxi_mad_work_resume);
spin_lock_init(&(sunxi_mad->resume_spin));
ret = sysfs_create_group(&pdev->dev.kobj, &audio_mad_debug_attr_group);
if (ret) {
dev_err(&pdev->dev, "failed to create mad attr group.\n");
#ifdef SUNXI_MAD_NETLINK_USE
goto err_mad_netlink_exit;
#else
goto err_mad_irq_free;
#endif
}
pr_debug("[audio] sunxi mad probe succeed!\n");
return 0;
#ifdef SUNXI_MAD_NETLINK_USE
err_mad_netlink_exit:
sunxi_mad_netlink_exit();
#endif
err_mad_irq_free:
devm_free_irq(sunxi_mad->dev, sunxi_mad->mad_irq, sunxi_mad);
err_dev_wakeup_irq:
if (sunxi_mad->wakeup_flag & SUNXI_MAD_WAKEUP_USE)
dev_pm_clear_wake_irq(&pdev->dev);
err_dev_init_wakeup:
if (sunxi_mad->wakeup_flag & SUNXI_MAD_WAKEUP_ON)
device_init_wakeup(&pdev->dev, false);
err_lpsd_irq_free:
devm_free_irq(sunxi_mad->dev, sunxi_mad->lpsd_irq, sunxi_mad);
err_mad_cfg_clk_disable:
#ifdef MAD_CLK_ALWAYS_ON
clk_disable_unprepare(sunxi_mad->mad_cfg_clk);
err_mad_cfg_clk_put:
#endif
clk_put(sunxi_mad->mad_cfg_clk);
err_mad_ad_clk_disable:
#ifdef MAD_CLK_ALWAYS_ON
clk_disable_unprepare(sunxi_mad->mad_ad_clk);
err_mad_ad_clk_put:
#endif
clk_put(sunxi_mad->mad_ad_clk);
err_mad_clk_disable:
#ifdef MAD_CLK_ALWAYS_ON
clk_disable_unprepare(sunxi_mad->mad_clk);
err_mad_clk_put:
#endif
clk_put(sunxi_mad->mad_clk);
err_lpsd_clk_disable:
#ifdef SUNXI_LPSD_CLK_ALWAYS_ON
clk_disable_unprepare(sunxi_mad->lpsd_clk);
err_lpsd_src_clk_disable:
#endif
if (sunxi_mad->pll_audio_src_used == 1)
clk_disable_unprepare(sunxi_mad->pll_clk);
else if (sunxi_mad->hosc_src_used == 1)
clk_disable_unprepare(sunxi_mad->hosc_clk);
err_lpsd_clk_put:
clk_put(sunxi_mad->lpsd_clk);
err_lpsd_src_clk_put:
if (sunxi_mad->pll_audio_src_used == 1)
clk_put(sunxi_mad->pll_clk);
else if (sunxi_mad->hosc_src_used == 1)
clk_put(sunxi_mad->hosc_clk);
err_iounmap:
iounmap(sunxi_mad->membase);
err_devm_kfree:
devm_kfree(&pdev->dev, sunxi_mad);
err_node_put:
of_node_put(np);
return ret;
}
static int __exit sunxi_mad_remove(struct platform_device *pdev)
{
struct sunxi_mad_info *sunxi_mad = dev_get_drvdata(&pdev->dev);
cancel_work_sync(&(sunxi_mad->ws_resume));
if (sunxi_mad->wakeup_flag & SUNXI_MAD_WAKEUP_USE)
dev_pm_clear_wake_irq(&pdev->dev);
if (sunxi_mad->wakeup_flag & SUNXI_MAD_WAKEUP_ON)
device_init_wakeup(&pdev->dev, false);
devm_free_irq(sunxi_mad->dev, sunxi_mad->mad_irq, sunxi_mad);
devm_free_irq(sunxi_mad->dev, sunxi_mad->lpsd_irq, sunxi_mad);
#ifdef MAD_CLK_ALWAYS_ON
clk_disable_unprepare(sunxi_mad->mad_clk);
clk_disable_unprepare(sunxi_mad->mad_ad_clk);
clk_disable_unprepare(sunxi_mad->mad_cfg_clk);
#endif
clk_put(sunxi_mad->mad_clk);
clk_put(sunxi_mad->mad_ad_clk);
clk_put(sunxi_mad->mad_cfg_clk);
#ifdef SUNXI_LPSD_CLK_ALWAYS_ON
clk_disable_unprepare(sunxi_mad->lpsd_clk);
#endif
clk_put(sunxi_mad->lpsd_clk);
if (sunxi_mad->pll_audio_src_used == 1) {
clk_disable_unprepare(sunxi_mad->pll_clk);
clk_put(sunxi_mad->pll_clk);
} else if (sunxi_mad->hosc_src_used == 1) {
clk_disable_unprepare(sunxi_mad->hosc_clk);
clk_put(sunxi_mad->hosc_clk);
}
#ifdef SUNXI_MAD_NETLINK_USE
sunxi_mad_netlink_exit();
#endif
iounmap(sunxi_mad->membase);
devm_kfree(&pdev->dev, sunxi_mad);
return 0;
}
static const struct dev_pm_ops sunxi_mad_pm_ops = {
.suspend = sunxi_mad_suspend,
.resume = sunxi_mad_resume,
};
static const struct of_device_id sunxi_mad_of_match[] = {
{ .compatible = "allwinner,sunxi-mad", },
{ },
};
MODULE_DEVICE_TABLE(of, sunxi_mad_of_match);
static struct platform_driver sunxi_mad_driver = {
.probe = sunxi_mad_probe,
.remove = __exit_p(sunxi_mad_remove),
.driver = {
.name = DRV_NAME,
.owner = THIS_MODULE,
.pm = &sunxi_mad_pm_ops,
.of_match_table = sunxi_mad_of_match,
},
};
module_platform_driver(sunxi_mad_driver);
MODULE_AUTHOR("qinzhenying <qinzhenying@allwinnertech.com>");
MODULE_DESCRIPTION("SUNXI MAD driver");
MODULE_ALIAS("platform:sunxi-mad");
MODULE_LICENSE("GPL");