/* * linux/arch/arm/mach-tegra/tegra_das.c * * Digital audio switch driver for tegra soc * * Copyright (C) 2010-2011 NVIDIA Corporation * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include #include #include #include #include #include #include #include "clock.h" #include #include #include #define TOTAL_DAP_PORTS 5 struct das_driver_context { struct platform_device *pdev; struct tegra_das_platform_data *pdata; phys_addr_t das_phys; unsigned long das_base; struct mutex mlock; unsigned long tristate_count; struct tegra_das_mux_select *mux_table; enum tegra_das_port_con_id cur_con_id; u32 total_dap_ports; tegra_das_port hifi_port_idx; tegra_das_port voice_codec_idx; tegra_das_port bb_port_idx; tegra_das_port bt_port_idx; tegra_das_port fm_radio_port_idx; struct clk *mclk; int mclk_refcnt; int mclk_rate; int mclk_parent; }; struct das_driver_context *das_drv_data; #define SET_DAP_REG_FIELDS(dap_port, reg_off, mux_val) \ { \ .port_type = dap_port, \ .reg_offset = reg_off, \ .mux_mask = DAP_CTRL_SEL_DEFAULT_MASK, \ .mux_shift = DAP_CTRL_SEL_SHIFT, \ .sdata1_mask = DAP_SDATA1_TX_RX_DEFAULT_MASK, \ .sdata1_shift = DAP_SDATA1_TX_RX_SHIFT, \ .sdata2_mask = DAP_SDATA2_RX_TX_DEFAULT_MASK, \ .sdata2_shift = DAP_SDATA2_RX_TX_SHIFT, \ .ms_mode_mask = DAP_MS_SEL_DEFAULT_MASK, \ .ms_mode_shift = DAP_MS_SEL_SHIFT, \ .mux_value = mux_val, \ } #define SET_DAC_REG_FIELDS(dac_port, reg_off, mux_val) \ { \ .port_type = dac_port, \ .reg_offset = reg_off, \ .mux_mask = DAC_CLK_SEL_DEFAULT_MASK, \ .mux_shift = DAC_CLK_SEL_SHIFT, \ .sdata1_mask = DAC_SDATA1_SEL_DEFAULT_MASK, \ .sdata1_shift = DAC_SDATA1_SEL_SHIFT, \ .sdata2_mask = DAC_SDATA2_SEL_DEFAULT_MASK, \ .sdata2_shift = DAC_SDATA2_SEL_SHIFT, \ .ms_mode_mask = 0, \ .ms_mode_shift = 0, \ .mux_value = mux_val, \ } struct tegra_das_mux_select das_mux_table[] = { {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, /* set DAP control registers fields */ SET_DAP_REG_FIELDS(tegra_das_port_dap1, APB_MISC_DAS_DAP_CTRL_SEL_0, DAP_CTRL_SEL_DAP1), SET_DAP_REG_FIELDS(tegra_das_port_dap2, APB_MISC_DAS_DAP_CTRL_SEL_1, DAP_CTRL_SEL_DAP2), SET_DAP_REG_FIELDS(tegra_das_port_dap3, APB_MISC_DAS_DAP_CTRL_SEL_2, DAP_CTRL_SEL_DAP3), SET_DAP_REG_FIELDS(tegra_das_port_dap4, APB_MISC_DAS_DAP_CTRL_SEL_3, DAP_CTRL_SEL_DAP4), SET_DAP_REG_FIELDS(tegra_das_port_dap5, APB_MISC_DAS_DAP_CTRL_SEL_4, DAP_CTRL_SEL_DAP5), /* set DAC control registers fields */ SET_DAC_REG_FIELDS(tegra_das_port_i2s1, APB_MISC_DAS_DAC_INPUT_DATA_CLK_SEL_0, DAP_CTRL_SEL_DAC1), SET_DAC_REG_FIELDS(tegra_das_port_i2s2, APB_MISC_DAS_DAC_INPUT_DATA_CLK_SEL_1, DAP_CTRL_SEL_DAC2), SET_DAC_REG_FIELDS(tegra_das_port_ac97, APB_MISC_DAS_DAC_INPUT_DATA_CLK_SEL_2, DAP_CTRL_SEL_DAC3), }; static int das_set_pin_state(bool normal); #ifdef CONFIG_DEBUG_FS #include #include static int dbg_das_show(struct seq_file *s, void *unused) { struct das_driver_context *ctx = s->private; int i; u32 base_add = ctx->das_base; u32 reg_off; seq_printf(s, "Digital Audio Switch Registers \n"); seq_printf(s, "------------------------------\n"); for (i = 0; i < ARRAY_SIZE(das_mux_table); i++) { reg_off = das_mux_table[i].reg_offset; seq_printf(s, "%4X: 0x%08X \n", (reg_off), readl(base_add + reg_off)); } return 0; } static int dbg_das_open(struct inode *inode, struct file *file) { return single_open(file, dbg_das_show, inode->i_private); } static const struct file_operations debug_fops = { .open = dbg_das_open, .read = seq_read, .llseek = seq_lseek, .release = single_release, }; static void tegra_das_debuginit(struct das_driver_context *ctx) { (void) debugfs_create_file("tegra_das", S_IRUGO, NULL, ctx, &debug_fops); } #else static void __init tegra_das_debuginit(void) { return; } #endif static inline void das_writel(unsigned long base, u32 val, u32 reg) { writel(val, base + reg); } static inline u32 das_readl(unsigned long base, u32 reg) { return readl(base + reg); } static int das_set_mux_ctrl_reg(u32 src_idx, u32 dest_idx) { u32 reg_val = 0, reg_off = 0; u32 dest_mux_sel_field = 0, dest_mux_sel_val = 0; u32 mask = 0, shift = 0; if (dest_idx == tegra_das_port_none) { dest_idx = src_idx; } mask = das_drv_data->mux_table[dest_idx].mux_mask; shift = das_drv_data->mux_table[dest_idx].mux_shift; dest_mux_sel_field = mask << shift; dest_mux_sel_val = das_drv_data->mux_table[src_idx].mux_value; reg_off = das_drv_data->mux_table[dest_idx].reg_offset; if (dest_idx > tegra_das_port_dap5) dest_mux_sel_val = src_idx - tegra_das_port_dap1; /* read the default DAS/DAP register */ reg_val = das_readl(das_drv_data->das_base, reg_off); /* clear the exiting selection bits */ reg_val &= ~(dest_mux_sel_field); /* set the destination value */ reg_val |= (dest_mux_sel_val & mask) << shift; if (dest_idx > tegra_das_port_dap5) { mask = das_drv_data->mux_table[dest_idx].sdata2_mask; shift = das_drv_data->mux_table[dest_idx].sdata2_shift; dest_mux_sel_field = mask << shift; reg_val &= ~(dest_mux_sel_field); reg_val |= (dest_mux_sel_val & mask) << shift; mask = das_drv_data->mux_table[dest_idx].sdata1_mask; shift = das_drv_data->mux_table[dest_idx].sdata1_shift; dest_mux_sel_field = mask << shift; reg_val &= ~(dest_mux_sel_field); reg_val |= (dest_mux_sel_val & mask) << shift; } das_writel(das_drv_data->das_base, reg_val, reg_off); return 0; } /* * function to set dap as master/slave, when two or more DAPs * are in by-pass mode */ static int das_set_dap_ms_mode(u32 dap_port_idx, bool is_master_mode) { u32 reg_val = 0; u32 mask = 0, shift = 0; u32 src_mode = 0; u32 reg_off = das_drv_data->mux_table[dap_port_idx].reg_offset; mask = das_drv_data->mux_table[dap_port_idx].ms_mode_mask; shift = das_drv_data->mux_table[dap_port_idx].ms_mode_shift; src_mode = mask << shift; /* nothing to do for the None Port */ if (dap_port_idx == tegra_das_port_none) return 0; /* Read the default DAS/DAP Register */ reg_val = das_readl(das_drv_data->das_base, reg_off); /* Clear the Mode bits */ reg_val &= ~(src_mode); if (is_master_mode) { reg_val |= (1 & mask) << shift; } das_writel(das_drv_data->das_base, reg_val, reg_off); return 0; } static int das_set_pin_state(bool normal) { const struct tegra_dap_property *dap_info = &das_drv_data->pdata->tegra_dap_port_info_table[0]; mutex_lock(&das_drv_data->mlock); if (normal) { if (das_drv_data->tristate_count == 0) { /* Enable the DAP outputs */ if (dap_info[0].dap_port != tegra_das_port_none) tegra_pinmux_set_tristate(TEGRA_PINGROUP_DAP1, TEGRA_TRI_NORMAL); if (dap_info[1].dap_port != tegra_das_port_none) tegra_pinmux_set_tristate(TEGRA_PINGROUP_DAP2, TEGRA_TRI_NORMAL); if (dap_info[2].dap_port != tegra_das_port_none) tegra_pinmux_set_tristate(TEGRA_PINGROUP_DAP3, TEGRA_TRI_NORMAL); if (dap_info[3].dap_port != tegra_das_port_none) tegra_pinmux_set_tristate(TEGRA_PINGROUP_DAP4, TEGRA_TRI_NORMAL); } das_drv_data->tristate_count++; } else { das_drv_data->tristate_count--; /* Tristate the DAP pinmux */ if (das_drv_data->tristate_count == 0) { if (dap_info[0].dap_port != tegra_das_port_none) tegra_pinmux_set_tristate(TEGRA_PINGROUP_DAP1, TEGRA_TRI_TRISTATE); if (dap_info[1].dap_port != tegra_das_port_none) tegra_pinmux_set_tristate(TEGRA_PINGROUP_DAP2, TEGRA_TRI_TRISTATE); if (dap_info[2].dap_port != tegra_das_port_none) tegra_pinmux_set_tristate(TEGRA_PINGROUP_DAP3, TEGRA_TRI_TRISTATE); if (dap_info[3].dap_port != tegra_das_port_none) tegra_pinmux_set_tristate(TEGRA_PINGROUP_DAP4, TEGRA_TRI_TRISTATE); } } mutex_unlock(&das_drv_data->mlock); return 0; } static int tegra_dap_default_settings(tegra_das_port dest_port) { tegra_das_port src_port = tegra_das_port_i2s1; const struct tegra_dap_property *dap_port_info_tbl; if ((dest_port <= tegra_das_port_none) || (dest_port > tegra_das_port_dap5)) { return 0; } dap_port_info_tbl = das_drv_data->pdata->tegra_dap_port_info_table; src_port = dap_port_info_tbl[dest_port].dac_port; das_set_mux_ctrl_reg(src_port, dest_port); /* Set the Port to Slave Mode */ das_set_dap_ms_mode(dest_port, false); return 0; } const struct tegra_das_con* get_con_table_entry( enum tegra_das_port_con_id con_id) { const struct tegra_das_con *ptable = das_drv_data->pdata->tegra_das_con_table; int i; for (i = 0; i < tegra_das_port_con_id_max; i++) { if (con_id == ptable[i].con_id) { return &ptable[i]; } } return NULL; } static int das_set_con_end_points(u32 src_idx, u32 dest_idx, bool is_src_master) { if (src_idx == tegra_das_port_none && dest_idx == tegra_das_port_none) return 0; /* src to dest index */ das_set_mux_ctrl_reg(src_idx, dest_idx); /* If src_idx is None swap the src and dest */ if (src_idx == tegra_das_port_none) src_idx = dest_idx; /* set the master/slave mode for source port */ das_set_dap_ms_mode(src_idx, is_src_master); /* set the master/slave mode for destination port */ das_set_dap_ms_mode(dest_idx, !(is_src_master)); return 0; } int tegra_das_set_connection(enum tegra_das_port_con_id new_con_id) { int i; const struct tegra_das_con *pcon = NULL; /* do nothng if same connection is requested */ if (das_drv_data->cur_con_id == new_con_id) return 0; pcon = get_con_table_entry(new_con_id); mutex_lock(&das_drv_data->mlock); if (pcon) { for (i = 0; i < pcon->num_entries; i++) { das_set_con_end_points(pcon->con_line[i].src, pcon->con_line[i].dest, pcon->con_line[i].src_master); } } das_drv_data->cur_con_id = new_con_id; mutex_unlock(&das_drv_data->mlock); return 0; } EXPORT_SYMBOL_GPL(tegra_das_set_connection); int tegra_das_get_connection(void) { enum tegra_das_port_con_id con_id; mutex_lock(&das_drv_data->mlock); con_id = das_drv_data->cur_con_id; mutex_unlock(&das_drv_data->mlock); return con_id; } EXPORT_SYMBOL_GPL(tegra_das_get_connection); bool tegra_das_is_port_master(enum tegra_audio_codec_type codec_type) { const struct tegra_das_con *con_table = &das_drv_data->pdata->tegra_das_con_table[0]; const struct tegra_dap_property *dap_info = &das_drv_data->pdata->tegra_dap_port_info_table[0]; int con_id, i; tegra_das_port dap_port = 0; con_id = tegra_das_get_connection(); for (i = 0; i < tegra_das_port_con_id_max; i++, con_table++) { if (con_id == con_table->con_id) break; } if (i == tegra_das_port_con_id_max) return false; for (i = 0; i < MAX_DAP_PORTS; i++) { if (dap_info[i].codec_type == codec_type) { dap_port = dap_info[i].dap_port; break; } } if (i == MAX_DAP_PORTS) return false; for (i = 0; i < con_table->num_entries; i++) { const struct tegra_das_con_line* con = &con_table->con_line[i]; if ((con->src == dap_port) && con->src_master) return true; else if((con->dest == dap_port) && !con->src_master) return true; } return false; } EXPORT_SYMBOL_GPL(tegra_das_is_port_master); int tegra_das_get_codec_data_fmt(enum tegra_audio_codec_type codec_type) { const struct tegra_das_con *con_table = &das_drv_data->pdata->tegra_das_con_table[0]; const struct tegra_dap_property *dap_info = das_drv_data->pdata->tegra_dap_port_info_table; int con_id, i; tegra_das_port dap_a = 0, dap_b = 0; enum dac_dap_data_format data_fmt = dac_dap_data_format_all; con_id = tegra_das_get_connection(); for (i = 0; i < tegra_das_port_con_id_max; i++, con_table++) { if (con_id == con_table->con_id) break; } if (i == tegra_das_port_con_id_max) return data_fmt; for (i = 0; i < MAX_DAP_PORTS; i++) { if (dap_info[i].codec_type == codec_type) { dap_a = dap_info[i].dap_port; data_fmt = dap_info[i].device_property. dac_dap_data_comm_format; break; } } if (i == MAX_DAP_PORTS) return data_fmt; for (i = 0; i < con_table->num_entries; i++) { const struct tegra_das_con_line* con = &con_table->con_line[i]; if (con->src == dap_a) { dap_b = con->dest; break; } else if (con->dest == dap_a) { dap_b = con->src; break; } } /* For dac-dap connection return codec data format */ if (dap_b > tegra_das_port_dap5) return data_fmt; /* For dap-dap bypass connection return data format supported by codecs connected to each dap port */ for (i = 0; i < MAX_DAP_PORTS; i++) { if (dap_info[i].dap_port == dap_b) { data_fmt &= dap_info[i].device_property. dac_dap_data_comm_format; break; } } return data_fmt; } EXPORT_SYMBOL_GPL(tegra_das_get_codec_data_fmt); struct clk* tegra_das_get_dap_mclk(void) { return tegra_get_clock_by_name(das_drv_data->pdata->dap_clk); } EXPORT_SYMBOL_GPL(tegra_das_get_dap_mclk); /* if is_normal is true then power mode is normal else tristated */ int tegra_das_power_mode(bool is_normal) { das_set_pin_state(is_normal); return 0; } EXPORT_SYMBOL_GPL(tegra_das_power_mode); int tegra_das_open(void) { int err = 0; das_drv_data->mclk = tegra_get_clock_by_name("clk_dev1"); if (!das_drv_data->mclk) err = -ENODEV; return err; } EXPORT_SYMBOL_GPL(tegra_das_open); int tegra_das_close(void) { if (das_drv_data->mclk) clk_put(das_drv_data->mclk); return 0; } EXPORT_SYMBOL_GPL(tegra_das_close); int tegra_das_set_mclk_parent(int parent) { /* FIXME ; set parent based on need */ struct clk *mclk_source = clk_get_sys(NULL, "pll_a_out0"); clk_set_parent(das_drv_data->mclk, mclk_source); return 0; } EXPORT_SYMBOL_GPL(tegra_das_set_mclk_parent); int tegra_das_enable_mclk(void) { int err = 0; if (!das_drv_data->mclk_refcnt && das_drv_data->mclk) { tegra_das_set_mclk_parent(0); if (clk_enable(das_drv_data->mclk)) { err = PTR_ERR(das_drv_data->mclk); return err; } } das_drv_data->mclk_refcnt++; return err; } EXPORT_SYMBOL_GPL(tegra_das_enable_mclk); int tegra_das_disable_mclk(void) { int err = 0; if (das_drv_data->mclk_refcnt > 0) { das_drv_data->mclk_refcnt--; if (das_drv_data->mclk_refcnt == 0) { if (das_drv_data->mclk) clk_disable(das_drv_data->mclk); } } return err; } EXPORT_SYMBOL_GPL(tegra_das_disable_mclk); int tegra_das_set_mclk_rate(int rate) { /* FIXME: change the clock after disabling it if needed */ das_drv_data->mclk_rate = rate; clk_set_rate(das_drv_data->mclk, rate); return 0; } EXPORT_SYMBOL_GPL(tegra_das_set_mclk_rate); int tegra_das_get_mclk_rate(void) { if (!das_drv_data->mclk) return -ENODEV; return clk_get_rate(das_drv_data->mclk); } EXPORT_SYMBOL_GPL(tegra_das_get_mclk_rate); static int tegra_das_probe(struct platform_device *pdev) { int rc = 0; struct resource *res; struct das_driver_context *das_ctx; const struct tegra_dap_property *dap_prop; bool found; int i; das_ctx = kzalloc(sizeof(*das_ctx), GFP_KERNEL); if (!das_ctx) return -ENOMEM; das_drv_data = das_ctx; das_ctx->pdev = pdev; das_ctx->pdata = pdev->dev.platform_data; das_ctx->pdata->driver_data = das_ctx; BUG_ON(!das_ctx->pdata); das_ctx->mux_table = das_mux_table; das_ctx->total_dap_ports = TOTAL_DAP_PORTS; das_ctx->cur_con_id = tegra_das_port_con_id_none; res = platform_get_resource(pdev, IORESOURCE_MEM, 0); if (!res) { dev_err(&pdev->dev, "no mem resource!\n"); rc = -ENODEV; goto err; } if (!request_mem_region(res->start, resource_size(res), pdev->name)) { dev_err(&pdev->dev, "memory region already claimed!\n"); rc = -ENOMEM; goto err; } das_ctx->das_phys = res->start; das_ctx->das_base = (unsigned long)ioremap(res->start, res->end - res->start + 1); if (!das_ctx->das_base) { dev_err(&pdev->dev, "cannot remap iomem!\n"); rc = -EIO; goto err; } mutex_init(&das_ctx->mlock); platform_set_drvdata(pdev, das_ctx); tegra_das_debuginit(das_ctx); for (i = 0; i <= tegra_das_port_dap5; i++) { dap_prop = &(das_ctx->pdata->tegra_dap_port_info_table[i]); found = true; /* For low power consumption - default the values as follows 0x7000009c = 0x00000248, This sets DAP2, 3, 4 connected to DAC2 0x70000014 = 0x00000700, SDOUT2,3,4 are input */ /* Obtain the port index for each codec type */ switch(dap_prop->codec_type) { case tegra_audio_codec_type_hifi: das_ctx->hifi_port_idx = dap_prop->dap_port; break; case tegra_audio_codec_type_voice: das_ctx->voice_codec_idx = dap_prop->dap_port; break; case tegra_audio_codec_type_bluetooth: das_ctx->bt_port_idx = dap_prop->dap_port; break; case tegra_audio_codec_type_baseband: das_ctx->bb_port_idx = dap_prop->dap_port; break; case tegra_audio_codec_type_fm_radio: das_ctx->fm_radio_port_idx = dap_prop->dap_port; break; default: found = false; break; } if (found) { tegra_dap_default_settings(dap_prop->dap_port); } } /* by default connect to hifi codec */ tegra_das_set_connection(tegra_das_port_con_id_hifi); return 0; err: kfree(das_ctx); return rc; } static struct platform_driver tegra_das_driver = { .driver = { .name = "tegra_das", .owner = THIS_MODULE, }, .probe = tegra_das_probe, }; static int __init tegra_das_init(void) { return platform_driver_register(&tegra_das_driver); } module_init(tegra_das_init); void tegra_das_get_all_regs(struct das_regs_cache* regs) { unsigned long reg_base = das_drv_data->das_base; regs->das_dap_ctrl_0 = das_readl(reg_base, APB_MISC_DAS_DAP_CTRL_SEL_0); regs->das_dap_ctrl_1 = das_readl(reg_base, APB_MISC_DAS_DAP_CTRL_SEL_1); regs->das_dap_ctrl_2 = das_readl(reg_base, APB_MISC_DAS_DAP_CTRL_SEL_2); regs->das_dap_ctrl_3 = das_readl(reg_base, APB_MISC_DAS_DAP_CTRL_SEL_3); regs->das_dap_ctrl_4 = das_readl(reg_base, APB_MISC_DAS_DAP_CTRL_SEL_4); regs->das_dac_input_data_clk_0 = das_readl(reg_base, APB_MISC_DAS_DAC_INPUT_DATA_CLK_SEL_0); regs->das_dac_input_data_clk_1 = das_readl(reg_base, APB_MISC_DAS_DAC_INPUT_DATA_CLK_SEL_1); regs->das_dac_input_data_clk_2 = das_readl(reg_base, APB_MISC_DAS_DAC_INPUT_DATA_CLK_SEL_2); } EXPORT_SYMBOL_GPL(tegra_das_get_all_regs); void tegra_das_set_all_regs(struct das_regs_cache* regs) { unsigned long reg_base = das_drv_data->das_base; das_writel(reg_base, regs->das_dap_ctrl_0, APB_MISC_DAS_DAP_CTRL_SEL_0); das_writel(reg_base, regs->das_dap_ctrl_1, APB_MISC_DAS_DAP_CTRL_SEL_1); das_writel(reg_base, regs->das_dap_ctrl_2, APB_MISC_DAS_DAP_CTRL_SEL_2); das_writel(reg_base, regs->das_dap_ctrl_3, APB_MISC_DAS_DAP_CTRL_SEL_3); das_writel(reg_base, regs->das_dap_ctrl_4, APB_MISC_DAS_DAP_CTRL_SEL_4); das_writel(reg_base, regs->das_dac_input_data_clk_0, APB_MISC_DAS_DAC_INPUT_DATA_CLK_SEL_0); das_writel(reg_base, regs->das_dac_input_data_clk_1, APB_MISC_DAS_DAC_INPUT_DATA_CLK_SEL_1); das_writel(reg_base, regs->das_dac_input_data_clk_2, APB_MISC_DAS_DAC_INPUT_DATA_CLK_SEL_2); } EXPORT_SYMBOL_GPL(tegra_das_set_all_regs); MODULE_LICENSE("GPL");