diff -drupN a/sound/soc/sunxi/sunxi-mad.c b/sound/soc/sunxi/sunxi-mad.c --- a/sound/soc/sunxi/sunxi-mad.c 1970-01-01 03:00:00.000000000 +0300 +++ b/sound/soc/sunxi/sunxi-mad.c 2022-06-12 05:28:14.000000000 +0300 @@ -0,0 +1,1805 @@ +/* + * sound\soc\sunxi\sunxi-mad.c + * (C) Copyright 2018-2023 + * AllWinner Technology Co., Ltd. + * wolfgang + * yumingfeng + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "sunxi-mad.h" +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#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, ®_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, ®_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, ®_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, ®_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, ®_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, ®_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, ®_val); + snd_printk("SUNXI_MAD_DMA_TF_SIZE:0x%x\n", reg_val); + + regmap_read(sunxi_mad->regmap, SUNXI_MAD_RD_SIZE, ®_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, ®_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, ®_val[0]); + regmap_read(sunxi_mad->regmap, reg_offset + 0x4, ®_val[1]); + regmap_read(sunxi_mad->regmap, reg_offset + 0x8, ®_val[2]); + regmap_read(sunxi_mad->regmap, reg_offset + 0xc, ®_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, ®_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 "); +MODULE_DESCRIPTION("SUNXI MAD driver"); +MODULE_ALIAS("platform:sunxi-mad"); +MODULE_LICENSE("GPL");