/* * omap3h1.c -- SoC audio for OMAP3 H1 * * Author: Evan Wilson * * Adapted from omap3pandora * Author: Steve Sakoman * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * 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. * * 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 St, Fifth Floor, Boston, MA * 02110-1301 USA * */ //#define DEBUG #include #include #include #include #include #include #include #include #include #include #include #include "omap-mcbsp.h" static struct clk *per_96m_fck; static unsigned long rate; static int mic_gpio_enable; static int omap3h1_hw_in_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params) { struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_dai *cpu_dai = rtd->cpu_dai; int ret = 0; pr_info("ASoc OMAP3H1: omap3h1_hw_in_params\n"); pr_info("ASoc OMAP3H1: in params channels=%d\n", params_channels(params)); pr_info("ASoc OMAP3H1: in params rate=%d\n", params_rate(params)); pr_info("ASoc OMAP3H1: in params params_period_size=%d\n", params_period_size(params)); pr_info("ASoc OMAP3H1: in params params_periods=%d\n", params_periods(params)); pr_info("ASoc OMAP3H1: in params params_buffer_size=%d\n", params_buffer_size(params)); pr_info("ASoc OMAP3H1: in params params_buffer_bytes=%d\n", params_buffer_bytes(params)); /* Set McBSP clock to use PER_96M_FCLK */ ret = snd_soc_dai_set_sysclk(cpu_dai, OMAP_MCBSP_SYSCLK_CLKS_FCLK, rate, SND_SOC_CLOCK_OUT); if (ret < 0) { printk(KERN_ERR "can't set DMIC cpu system clock\n"); return ret; } /* The microphone becomes operational 2^18 clock cycles */ /* (85 ms with SCK at 3.072 MHz) */ ret = snd_soc_dai_set_clkdiv(cpu_dai, OMAP_MCBSP_CLKGDV, 32); if (ret < 0) { pr_err("ASoc OMAP3H1: can't set MIC clock divider\n"); return ret; } return 0; } static int omap3h1_hw_out_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params) { struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_dai *cpu_dai = rtd->cpu_dai; int ret = 0; pr_info("ASoc OMAP3H1: omap3h1_hw_out_params"); pr_info("ASoc OMAP3H1: out params channels=%u\n", params_channels(params)); pr_info("ASoc OMAP3H1: out params rate=%u\n", params_rate(params)); pr_info("ASoc OMAP3H1: out params params_period_size=%u\n", params_period_size(params)); pr_info("ASoc OMAP3H1: out params params_periods=%u\n", params_periods(params)); pr_info("ASoc OMAP3H1: out params params_buffer_size=%u\n", params_buffer_size(params)); pr_info("ASoc OMAP3H1: out params params_buffer_bytes=%u\n", params_buffer_bytes(params)); /* Set McBSP clock to use PER_96M_FCLK */ ret = snd_soc_dai_set_sysclk(cpu_dai, OMAP_MCBSP_SYSCLK_CLKS_FCLK, rate, SND_SOC_CLOCK_OUT); if (ret < 0) { printk(KERN_ERR "can't set DMIC cpu system clock\n"); return ret; } /* The BT operates on 512K freq */ ret = snd_soc_dai_set_clkdiv(cpu_dai, OMAP_MCBSP_CLKGDV, rate/512000); if (ret < 0) { pr_err("ASoc OMAP3H1: can't set BT clock divider\n"); return ret; } return 0; } static int omap3h1_in_startup(struct snd_pcm_substream *substream) { pr_info("ASoc OMAP3H1: omap3h1_in_startup"); gpio_set_value(mic_gpio_enable, 1); return clk_enable(per_96m_fck); } static void omap3h1_in_shutdown(struct snd_pcm_substream *substream) { pr_info("ASoc OMAP3H1: omap3h1_in_shutdown"); gpio_set_value(mic_gpio_enable, 0); clk_disable(per_96m_fck); } static int omap3h1_out_startup(struct snd_pcm_substream *substream) { pr_info("ASoc OMAP3H1: omap3h1_out_startup"); return clk_enable(per_96m_fck); } static void omap3h1_out_shutdown(struct snd_pcm_substream *substream) { pr_info("ASoc OMAP3H1: omap3h1_out_shutdown"); clk_disable(per_96m_fck); } static struct snd_soc_ops omap3h1_in_ops = { .hw_params = omap3h1_hw_in_params, .startup = omap3h1_in_startup, .shutdown = omap3h1_in_shutdown, }; static struct snd_soc_ops omap3h1_out_ops = { .hw_params = omap3h1_hw_out_params, .startup = omap3h1_out_startup, .shutdown = omap3h1_out_shutdown, }; /* Digital audio interface glue - connects codec <--> CPU */ static struct snd_soc_dai_link omap3h1_dai[] = { { .name = "DMIC", .stream_name = "DMIC Capture", .cpu_dai_name = "omap-mcbsp.3", .codec_dai_name = "dmic-hifi", .platform_name = "omap-pcm-audio", .codec_name = "dmic-codec", .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS, .ops = &omap3h1_in_ops, }, { .name = "BT", .stream_name = "BT Playback", .cpu_dai_name = "omap-mcbsp.2", .codec_dai_name = "omap3h1_bt_sco-pcm", .platform_name = "omap-pcm-audio", .codec_name = "omap3h1_bt_sco", .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS, .ops = &omap3h1_out_ops, } }; /* Audio machine driver */ static struct snd_soc_card snd_soc_omap3h1 = { .name = "omap3h1", .owner = THIS_MODULE, .dai_link = omap3h1_dai, .num_links = ARRAY_SIZE(omap3h1_dai), }; static int omap3h1_card_probe(struct platform_device *pdev) { struct device_node *node = pdev->dev.of_node; struct snd_soc_card *card = &snd_soc_omap3h1; int ret = 0; int gpio; #ifdef CONFIG_OF if (node) { struct device_node *dai_node1; struct device_node *dai_node2; dai_node1 = of_parse_phandle(node, "olio,mcbsp_mic", 0); if (!dai_node1) { dev_err(&pdev->dev, "mcbsp3 node is not provided\n"); return -EINVAL; } omap3h1_dai[0].cpu_dai_name = NULL; omap3h1_dai[0].cpu_of_node = dai_node1; dai_node2 = of_parse_phandle(node, "olio,mcbsp_bt", 0); if (!dai_node2) { dev_err(&pdev->dev, "mcbsp2 node is not provided\n"); return -EINVAL; } omap3h1_dai[1].cpu_dai_name = NULL; omap3h1_dai[1].cpu_of_node = dai_node2; } else { dev_err(&pdev->dev, "Missing node\n"); return -ENODEV; } gpio = of_get_named_gpio(node, "olio,mic_enable", 0); ret = (gpio < 0) ? -ENODEV : gpio_request(gpio, "mic_enable"); if (ret) { dev_err(&pdev->dev, "GPIO request error gpio=%d err=%d\n", gpio, ret); return ret; } gpio_direction_output(gpio, 0); mic_gpio_enable = gpio; #endif card->dev = &pdev->dev; snd_soc_card_set_drvdata(card, NULL); ret = snd_soc_register_card(card); if (ret) { dev_err(&pdev->dev, "snd_soc_register_card() failed: %d\n", ret); return ret; } per_96m_fck = clk_get(&pdev->dev, "per_96m_fck"); if (IS_ERR(per_96m_fck)) { dev_err(&pdev->dev, "could not get per_96m_fck clock\n"); return PTR_ERR(per_96m_fck); } rate = clk_get_rate(per_96m_fck); return 0; } static int omap3h1_card_remove(struct platform_device *pdev) { struct snd_soc_card *card = platform_get_drvdata(pdev); gpio_free(mic_gpio_enable); snd_soc_unregister_card(card); return 0; } static const struct of_device_id omap_soc_h1_of_match[] = { {.compatible = "olio,omap-soc-omap3h1", }, { }, }; MODULE_DEVICE_TABLE(of, omap_soc_h1_of_match); static struct platform_driver omap3h1_driver = { .driver = { .name = "omap-soc-omap3h1", .of_match_table = of_match_ptr(omap_soc_h1_of_match), .pm = &snd_soc_pm_ops, }, .probe = omap3h1_card_probe, .remove = omap3h1_card_remove, }; module_platform_driver(omap3h1_driver); MODULE_AUTHOR("Evan Wilson