/* * 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");