/* Copyright (c) 2009-2011, The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and * only version 2 as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * */ #include #include #include #include #include #include #include #include #include #include #include #define LPA_REG_WRITEL(drv, val, reg) writel(val, drv->baseaddr + reg) #define LPA_REG_READL(drv, reg) readl(drv->baseaddr + reg) /* bit 2:0 is reserved because watermarks have to be 64-bit aligned */ #define LLB_WATERMARK_VAL_MASK 0x00000003 #define LPA_STATUS_SBUF_EN 0x01 struct lpa_drv { void __iomem *baseaddr; u32 obuf_hlb_size; u32 dsp_proc_id; u32 app_proc_id; struct lpa_mem_config nosb_config; struct lpa_mem_config sb_config; u32 status; u32 watermark_bytes; u32 watermark_aheadtime; u32 sample_boundary; }; struct lpa_state { struct lpa_drv lpa_drv; /* One instance for now */ u32 assigned; struct mutex lpa_lock; }; struct lpa_state the_lpa_state; static void lpa_enable_codec(struct lpa_drv *lpa, bool enable) { u32 val; val = LPA_REG_READL(lpa, LPA_OBUF_CODEC); val = enable ? (val | LPA_OBUF_CODEC_CODEC_INTF_EN_BMSK) : (val & ~LPA_OBUF_CODEC_CODEC_INTF_EN_BMSK); val |= LPA_OBUF_CODEC_LOAD_BMSK; LPA_REG_WRITEL(lpa, val, LPA_OBUF_CODEC); mb(); } static void lpa_reset(struct lpa_drv *lpa) { u32 status; struct clk *adsp_clk; /* Need to make sure not disable clock while other device is enabled */ adsp_clk = clk_get(NULL, "adsp_clk"); if (!adsp_clk) { MM_ERR("failed to get adsp clk\n"); goto error; } clk_prepare_enable(adsp_clk); lpa_enable_codec(lpa, 0); LPA_REG_WRITEL(lpa, (LPA_OBUF_RESETS_MISR_RESET | LPA_OBUF_RESETS_OVERALL_RESET), LPA_OBUF_RESETS); do { status = LPA_REG_READL(lpa, LPA_OBUF_STATUS); } while (!(status & LPA_OBUF_STATUS_RESET_DONE)); LPA_REG_WRITEL(lpa, LPA_OBUF_ACK_RESET_DONE_BMSK, LPA_OBUF_ACK); mb(); clk_disable_unprepare(adsp_clk); clk_put(adsp_clk); error: return; } static void lpa_config_hlb_addr(struct lpa_drv *lpa) { u32 val, min_addr = 0, max_addr = min_addr + lpa->obuf_hlb_size; val = (min_addr & LPA_OBUF_HLB_MIN_ADDR_SEG_BMSK) | LPA_OBUF_HLB_MIN_ADDR_LOAD_BMSK; LPA_REG_WRITEL(lpa, val, LPA_OBUF_HLB_MIN_ADDR); val = max_addr & LPA_OBUF_HLB_MAX_ADDR_SEG_BMSK; LPA_REG_WRITEL(lpa, val, LPA_OBUF_HLB_MAX_ADDR); } static void lpa_powerup_mem_bank(struct lpa_drv *lpa, struct lpa_mem_bank_select *bank) { u32 status, val; status = LPA_REG_READL(lpa, LPA_OBUF_MEMORY_CONTROL); val = ((*((u32 *) bank)) << LPA_OBUF_MEM_CTL_PWRUP_SHFT) & LPA_OBUF_MEM_CTL_PWRUP_BMSK; val |= status; LPA_REG_WRITEL(lpa, val, LPA_OBUF_MEMORY_CONTROL); } static void lpa_enable_interrupt(struct lpa_drv *lpa, u32 proc_id) { u32 val; proc_id &= LPA_OBUF_INTR_EN_BMSK; val = 0x1 << proc_id; LPA_REG_WRITEL(lpa, val, LPA_OBUF_INTR_ENABLE); } static void lpa_config_llb_addr(struct lpa_drv *lpa, u32 min_addr, u32 max_addr) { u32 val; val = (min_addr & LPA_OBUF_LLB_MIN_ADDR_SEG_BMSK) | LPA_OBUF_LLB_MIN_ADDR_LOAD_BMSK; LPA_REG_WRITEL(lpa, val, LPA_OBUF_LLB_MIN_ADDR); val = max_addr & LPA_OBUF_LLB_MAX_ADDR_SEG_BMSK; LPA_REG_WRITEL(lpa, val, LPA_OBUF_LLB_MAX_ADDR); } static void lpa_config_sb_addr(struct lpa_drv *lpa, u32 min_addr, u32 max_addr) { u32 val; val = (min_addr & LPA_OBUF_SB_MIN_ADDR_SEG_BMSK) | LPA_OBUF_SB_MIN_ADDR_LOAD_BMSK; LPA_REG_WRITEL(lpa, val, LPA_OBUF_SB_MIN_ADDR); val = max_addr & LPA_OBUF_SB_MAX_ADDR_SEG_BMSK; LPA_REG_WRITEL(lpa, val, LPA_OBUF_SB_MAX_ADDR); } static void lpa_switch_sb(struct lpa_drv *lpa) { if (lpa->status & LPA_STATUS_SBUF_EN) { lpa_config_llb_addr(lpa, lpa->sb_config.llb_min_addr, lpa->sb_config.llb_max_addr); lpa_config_sb_addr(lpa, lpa->sb_config.sb_min_addr, lpa->sb_config.sb_max_addr); } else { lpa_config_llb_addr(lpa, lpa->nosb_config.llb_min_addr, lpa->nosb_config.llb_max_addr); lpa_config_sb_addr(lpa, lpa->nosb_config.sb_min_addr, lpa->nosb_config.sb_max_addr); } } static u8 lpa_req_wmark_id(struct lpa_drv *lpa) { return (u8) (LPA_REG_READL(lpa, LPA_OBUF_WMARK_ASSIGN) & LPA_OBUF_WMARK_ASSIGN_BMSK); } static void lpa_enable_llb_wmark(struct lpa_drv *lpa, u32 wmark_ctrl, u32 wmark_id, u32 cpu_id) { u32 val; wmark_id = (wmark_id > 3) ? 0 : wmark_id; val = LPA_REG_READL(lpa, LPA_OBUF_WMARK_n_LLB_ADDR(wmark_id)); val &= ~LPA_OBUF_LLB_WMARK_CTRL_BMSK; val &= ~LPA_OBUF_LLB_WMARK_MAP_BMSK; val |= (wmark_ctrl << LPA_OBUF_LLB_WMARK_CTRL_SHFT) & LPA_OBUF_LLB_WMARK_CTRL_BMSK; val |= (cpu_id << LPA_OBUF_LLB_WMARK_MAP_SHFT) & LPA_OBUF_LLB_WMARK_MAP_BMSK; LPA_REG_WRITEL(lpa, val, LPA_OBUF_WMARK_n_LLB_ADDR(wmark_id)); } static void lpa_enable_sb_wmark(struct lpa_drv *lpa, u32 wmark_ctrl, u32 cpu_id) { u32 val; val = LPA_REG_READL(lpa, LPA_OBUF_WMARK_SB); val &= ~LPA_OBUF_SB_WMARK_CTRL_BMSK; val &= ~LPA_OBUF_SB_WMARK_MAP_BMSK; val |= (wmark_ctrl << LPA_OBUF_SB_WMARK_CTRL_SHFT) & LPA_OBUF_SB_WMARK_CTRL_BMSK; val |= (cpu_id << LPA_OBUF_SB_WMARK_MAP_SHFT) & LPA_OBUF_SB_WMARK_MAP_BMSK; LPA_REG_WRITEL(lpa, val, LPA_OBUF_WMARK_SB); } static void lpa_enable_hlb_wmark(struct lpa_drv *lpa, u32 wmark_ctrl, u32 cpu_id) { u32 val; val = LPA_REG_READL(lpa, LPA_OBUF_WMARK_HLB); val &= ~LPA_OBUF_HLB_WMARK_CTRL_BMSK; val &= ~LPA_OBUF_HLB_WMARK_MAP_BMSK; val |= (wmark_ctrl << LPA_OBUF_HLB_WMARK_CTRL_SHFT) & LPA_OBUF_HLB_WMARK_CTRL_BMSK; val |= (cpu_id << LPA_OBUF_HLB_WMARK_MAP_SHFT) & LPA_OBUF_HLB_WMARK_MAP_BMSK; LPA_REG_WRITEL(lpa, val, LPA_OBUF_WMARK_HLB); } static void lpa_enable_utc(struct lpa_drv *lpa, bool enable, u32 cpu_id) { u32 val; val = (cpu_id << LPA_OBUF_UTC_CONFIG_MAP_SHFT) & LPA_OBUF_UTC_CONFIG_MAP_BMSK; enable = (enable ? 1 : 0); val = (enable << LPA_OBUF_UTC_CONFIG_EN_SHFT) & LPA_OBUF_UTC_CONFIG_EN_BMSK; LPA_REG_WRITEL(lpa, val, LPA_OBUF_UTC_CONFIG); } static void lpa_enable_mixing(struct lpa_drv *lpa, bool enable) { u32 val; val = LPA_REG_READL(lpa, LPA_OBUF_CONTROL); val = (enable ? val | LPA_OBUF_CONTROL_LLB_EN_BMSK : val & ~LPA_OBUF_CONTROL_LLB_EN_BMSK); LPA_REG_WRITEL(lpa, val, LPA_OBUF_CONTROL); } static void lpa_enable_mixer_saturation(struct lpa_drv *lpa, u32 buf_id, bool enable) { u32 val; val = LPA_REG_READL(lpa, LPA_OBUF_CONTROL); switch (buf_id) { case LPA_BUF_ID_LLB: val = enable ? (val | LPA_OBUF_CONTROL_LLB_SAT_EN_BMSK) : (val & ~LPA_OBUF_CONTROL_LLB_SAT_EN_BMSK); break; case LPA_BUF_ID_SB: val = enable ? (val | LPA_OBUF_CONTROL_SB_SAT_EN_BMSK) : (val & ~LPA_OBUF_CONTROL_SB_SAT_EN_BMSK); break; } LPA_REG_WRITEL(lpa, val, LPA_OBUF_CONTROL); } static void lpa_enable_obuf(struct lpa_drv *lpa, u32 buf_id, bool enable) { u32 val; val = LPA_REG_READL(lpa, LPA_OBUF_CONTROL); switch (buf_id) { case LPA_BUF_ID_HLB: val = enable ? (val | LPA_OBUF_CONTROL_HLB_EN_BMSK) : (val & ~LPA_OBUF_CONTROL_HLB_EN_BMSK); break; case LPA_BUF_ID_LLB: val = enable ? (val | LPA_OBUF_CONTROL_LLB_EN_BMSK) : (val & ~LPA_OBUF_CONTROL_LLB_EN_BMSK); break; case LPA_BUF_ID_SB: val = enable ? (val | LPA_OBUF_CONTROL_SB_EN_BMSK) : (val & ~LPA_OBUF_CONTROL_SB_EN_BMSK); break; } LPA_REG_WRITEL(lpa, val, LPA_OBUF_CONTROL); } struct lpa_drv *lpa_get(void) { struct lpa_mem_bank_select mem_bank; struct lpa_drv *ret_lpa = &the_lpa_state.lpa_drv; mutex_lock(&the_lpa_state.lpa_lock); if (the_lpa_state.assigned) { MM_ERR("LPA HW accupied\n"); ret_lpa = ERR_PTR(-EBUSY); goto error; } /* perform initialization */ lpa_reset(ret_lpa); /* Config adec param */ /* Initialize LLB/SB min/max address */ lpa_switch_sb(ret_lpa); /* Config HLB minx/max address */ lpa_config_hlb_addr(ret_lpa); /* Power up all memory bank for now */ mem_bank.b0 = 1; mem_bank.b1 = 1; mem_bank.b2 = 1; mem_bank.b3 = 1; mem_bank.b4 = 1; mem_bank.b5 = 1; mem_bank.b6 = 1; mem_bank.b7 = 1; mem_bank.b8 = 1; mem_bank.b9 = 1; mem_bank.b10 = 1; mem_bank.llb = 1; lpa_powerup_mem_bank(ret_lpa, &mem_bank); while (lpa_req_wmark_id(ret_lpa) != LPA_OBUF_WMARK_ASSIGN_DONE); lpa_enable_llb_wmark(ret_lpa, LPA_WMARK_CTL_DISABLED, 0, ret_lpa->dsp_proc_id); lpa_enable_llb_wmark(ret_lpa, LPA_WMARK_CTL_DISABLED, 1, ret_lpa->dsp_proc_id); lpa_enable_llb_wmark(ret_lpa, LPA_WMARK_CTL_DISABLED, 2, ret_lpa->app_proc_id); lpa_enable_llb_wmark(ret_lpa, LPA_WMARK_CTL_DISABLED, 3, ret_lpa->app_proc_id); lpa_enable_hlb_wmark(ret_lpa, LPA_WMARK_CTL_DISABLED, ret_lpa->dsp_proc_id); lpa_enable_sb_wmark(ret_lpa, LPA_WMARK_CTL_DISABLED, ret_lpa->dsp_proc_id); lpa_enable_utc(ret_lpa, 0, LPA_OBUF_UTC_CONFIG_NO_INTR); lpa_enable_mixing(ret_lpa, 1); lpa_enable_mixer_saturation(ret_lpa, LPA_BUF_ID_LLB, 1); lpa_enable_obuf(ret_lpa, LPA_BUF_ID_HLB, 0); lpa_enable_obuf(ret_lpa, LPA_BUF_ID_LLB, 1); if (ret_lpa->status & LPA_STATUS_SBUF_EN) { lpa_enable_mixer_saturation(ret_lpa, LPA_BUF_ID_SB, 1); lpa_enable_obuf(ret_lpa, LPA_BUF_ID_SB, 1); } lpa_enable_interrupt(ret_lpa, ret_lpa->dsp_proc_id); mb(); the_lpa_state.assigned++; error: mutex_unlock(&the_lpa_state.lpa_lock); return ret_lpa; } EXPORT_SYMBOL(lpa_get); void lpa_put(struct lpa_drv *lpa) { mutex_lock(&the_lpa_state.lpa_lock); if (!lpa || &the_lpa_state.lpa_drv != lpa) { MM_ERR("invalid arg\n"); goto error; } /* Deinitialize */ the_lpa_state.assigned--; error: mutex_unlock(&the_lpa_state.lpa_lock); } EXPORT_SYMBOL(lpa_put); int lpa_cmd_codec_config(struct lpa_drv *lpa, struct lpa_codec_config *config_ptr) { u32 sample_rate; u32 num_channels; u32 width; u32 val = 0; if (!lpa || !config_ptr) { MM_ERR("invalid parameters\n"); return -EINVAL; } switch (config_ptr->num_channels) { case 8: num_channels = LPA_NUM_CHAN_7P1; break; case 6: num_channels = LPA_NUM_CHAN_5P1; break; case 4: num_channels = LPA_NUM_CHAN_4_CHANNEL; break; case 2: num_channels = LPA_NUM_CHAN_STEREO; break; case 1: num_channels = LPA_NUM_CHAN_MONO; break; default: MM_ERR("unsupported number of channel\n"); goto error; } val |= (num_channels << LPA_OBUF_CODEC_NUM_CHAN_SHFT) & LPA_OBUF_CODEC_NUM_CHAN_BMSK; switch (config_ptr->sample_rate) { case 96000: sample_rate = LPA_SAMPLE_RATE_96KHZ; break; case 64000: sample_rate = LPA_SAMPLE_RATE_64KHZ; break; case 48000: sample_rate = LPA_SAMPLE_RATE_48KHZ; break; case 44100: sample_rate = LPA_SAMPLE_RATE_44P1KHZ; break; case 32000: sample_rate = LPA_SAMPLE_RATE_32KHZ; break; case 22050: sample_rate = LPA_SAMPLE_RATE_22P05KHZ; break; case 16000: sample_rate = LPA_SAMPLE_RATE_16KHZ; break; case 11025: sample_rate = LPA_SAMPLE_RATE_11P025KHZ; break; case 8000: sample_rate = LPA_SAMPLE_RATE_8KHZ; break; default: MM_ERR("unsupported sample rate \n"); goto error; } val |= (sample_rate << LPA_OBUF_CODEC_SAMP_SHFT) & LPA_OBUF_CODEC_SAMP_BMSK; switch (config_ptr->sample_width) { case 32: width = LPA_BITS_PER_CHAN_32BITS; break; case 24: width = LPA_BITS_PER_CHAN_24BITS; break; case 16: width = LPA_BITS_PER_CHAN_16BITS; break; default: MM_ERR("unsupported sample width \n"); goto error; } val |= (width << LPA_OBUF_CODEC_BITS_PER_CHAN_SHFT) & LPA_OBUF_CODEC_BITS_PER_CHAN_BMSK; val |= LPA_OBUF_CODEC_LOAD_BMSK; val |= (config_ptr->output_interface << LPA_OBUF_CODEC_INTF_SHFT) & LPA_OBUF_CODEC_INTF_BMSK; LPA_REG_WRITEL(lpa, val, LPA_OBUF_CODEC); mb(); return 0; error: return -EINVAL; } EXPORT_SYMBOL(lpa_cmd_codec_config); static int lpa_check_llb_clear(struct lpa_drv *lpa) { u32 val; val = LPA_REG_READL(lpa, LPA_OBUF_STATUS); return !(val & LPA_OBUF_STATUS_LLB_CLR_BMSK); } static void lpa_clear_llb(struct lpa_drv *lpa) { u32 val; val = LPA_REG_READL(lpa, LPA_OBUF_CONTROL); LPA_REG_WRITEL(lpa, (val | LPA_OBUF_CONTROL_LLB_CLR_CMD_BMSK), LPA_OBUF_CONTROL); lpa_enable_obuf(lpa, LPA_BUF_ID_LLB, 0); while (!lpa_check_llb_clear(lpa)) udelay(100); LPA_REG_WRITEL(lpa, val, LPA_OBUF_CONTROL); } int lpa_cmd_enable_codec(struct lpa_drv *lpa, bool enable) { u32 val; struct lpa_mem_bank_select mem_bank; MM_DBG(" %s\n", (enable ? "enable" : "disable")); if (!lpa) return -EINVAL; val = LPA_REG_READL(lpa, LPA_OBUF_CODEC); if (enable) { if (val & LPA_OBUF_CODEC_CODEC_INTF_EN_BMSK) return -EBUSY; /* Power up all memory bank for now */ mem_bank.b0 = 1; mem_bank.b1 = 1; mem_bank.b2 = 1; mem_bank.b3 = 1; mem_bank.b4 = 1; mem_bank.b5 = 1; mem_bank.b6 = 1; mem_bank.b7 = 1; mem_bank.b8 = 1; mem_bank.b9 = 1; mem_bank.b10 = 1; mem_bank.llb = 1; lpa_powerup_mem_bank(lpa, &mem_bank); /*clear LLB*/ lpa_clear_llb(lpa); lpa_enable_codec(lpa, 1); MM_DBG("LPA codec is enabled\n"); } else { if (val & LPA_OBUF_CODEC_CODEC_INTF_EN_BMSK) { lpa_enable_codec(lpa, 0); MM_DBG("LPA codec is disabled\n"); } else MM_ERR("LPA codec is already disable\n"); } mb(); return 0; } EXPORT_SYMBOL(lpa_cmd_enable_codec); static int lpa_probe(struct platform_device *pdev) { int rc = 0; struct resource *mem_src; struct msm_lpa_platform_data *pdata; MM_INFO("lpa probe\n"); if (!pdev || !pdev->dev.platform_data) { MM_ERR("no plaform data\n"); rc = -ENODEV; goto error; } mem_src = platform_get_resource_byname(pdev, IORESOURCE_MEM, "lpa"); if (!mem_src) { MM_ERR("LPA base address undefined\n"); rc = -ENODEV; goto error; } pdata = pdev->dev.platform_data; the_lpa_state.lpa_drv.baseaddr = ioremap(mem_src->start, (mem_src->end - mem_src->start) + 1); if (!the_lpa_state.lpa_drv.baseaddr) { rc = -ENOMEM; goto error; } the_lpa_state.lpa_drv.obuf_hlb_size = pdata->obuf_hlb_size; the_lpa_state.lpa_drv.dsp_proc_id = pdata->dsp_proc_id; the_lpa_state.lpa_drv.app_proc_id = pdata->app_proc_id; the_lpa_state.lpa_drv.nosb_config = pdata->nosb_config; the_lpa_state.lpa_drv.sb_config = pdata->sb_config; /* default to enable summing buffer */ the_lpa_state.lpa_drv.status = LPA_STATUS_SBUF_EN; error: return rc; } static int lpa_remove(struct platform_device *pdev) { iounmap(the_lpa_state.lpa_drv.baseaddr); return 0; } static struct platform_driver lpa_driver = { .probe = lpa_probe, .remove = lpa_remove, .driver = { .name = "lpa", .owner = THIS_MODULE, }, }; static int __init lpa_init(void) { the_lpa_state.assigned = 0; mutex_init(&the_lpa_state.lpa_lock); return platform_driver_register(&lpa_driver); } static void __exit lpa_exit(void) { platform_driver_unregister(&lpa_driver); } module_init(lpa_init); module_exit(lpa_exit); MODULE_DESCRIPTION("MSM LPA driver"); MODULE_LICENSE("GPL v2");