/* * rt298.c -- RT298 ALSA SoC audio codec driver * * Copyright 2015 Realtek Semiconductor Corp. * Author: Bard Liao <bardliao@realtek.com> * * 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. */ #include <linux/module.h> #include <linux/moduleparam.h> #include <linux/init.h> #include <linux/delay.h> #include <linux/pm.h> #include <linux/i2c.h> #include <linux/platform_device.h> #include <linux/spi/spi.h> #include <linux/acpi.h> #include <sound/core.h> #include <sound/pcm.h> #include <sound/pcm_params.h> #include <sound/soc.h> #include <sound/soc-dapm.h> #include <sound/initval.h> #include <sound/tlv.h> #include <sound/jack.h> #include <linux/workqueue.h> #include <sound/rt298.h> #include "rl6347a.h" #include "rt298.h" #define RT298_VENDOR_ID 0x10ec0298 struct rt298_priv { struct reg_default *index_cache; int index_cache_size; struct regmap *regmap; struct snd_soc_codec *codec; struct rt298_platform_data pdata; struct i2c_client *i2c; struct snd_soc_jack *jack; struct delayed_work jack_detect_work; int sys_clk; int clk_id; int is_hp_in; }; static const struct reg_default rt298_index_def[] = { { 0x01, 0xa5a8 }, { 0x02, 0x8e95 }, { 0x03, 0x0002 }, { 0x04, 0xaf67 }, { 0x08, 0x200f }, { 0x09, 0xd010 }, { 0x0a, 0x0100 }, { 0x0b, 0x0000 }, { 0x0d, 0x2800 }, { 0x0f, 0x0022 }, { 0x19, 0x0217 }, { 0x20, 0x0020 }, { 0x33, 0x0208 }, { 0x46, 0x0300 }, { 0x49, 0x4004 }, { 0x4f, 0x50c9 }, { 0x50, 0x3000 }, { 0x63, 0x1b02 }, { 0x67, 0x1111 }, { 0x68, 0x1016 }, { 0x69, 0x273f }, }; #define INDEX_CACHE_SIZE ARRAY_SIZE(rt298_index_def) static const struct reg_default rt298_reg[] = { { 0x00170500, 0x00000400 }, { 0x00220000, 0x00000031 }, { 0x00239000, 0x0000007f }, { 0x0023a000, 0x0000007f }, { 0x00270500, 0x00000400 }, { 0x00370500, 0x00000400 }, { 0x00870500, 0x00000400 }, { 0x00920000, 0x00000031 }, { 0x00935000, 0x000000c3 }, { 0x00936000, 0x000000c3 }, { 0x00970500, 0x00000400 }, { 0x00b37000, 0x00000097 }, { 0x00b37200, 0x00000097 }, { 0x00b37300, 0x00000097 }, { 0x00c37000, 0x00000000 }, { 0x00c37100, 0x00000080 }, { 0x01270500, 0x00000400 }, { 0x01370500, 0x00000400 }, { 0x01371f00, 0x411111f0 }, { 0x01439000, 0x00000080 }, { 0x0143a000, 0x00000080 }, { 0x01470700, 0x00000000 }, { 0x01470500, 0x00000400 }, { 0x01470c00, 0x00000000 }, { 0x01470100, 0x00000000 }, { 0x01837000, 0x00000000 }, { 0x01870500, 0x00000400 }, { 0x02050000, 0x00000000 }, { 0x02139000, 0x00000080 }, { 0x0213a000, 0x00000080 }, { 0x02170100, 0x00000000 }, { 0x02170500, 0x00000400 }, { 0x02170700, 0x00000000 }, { 0x02270100, 0x00000000 }, { 0x02370100, 0x00000000 }, { 0x01870700, 0x00000020 }, { 0x00830000, 0x000000c3 }, { 0x00930000, 0x000000c3 }, { 0x01270700, 0x00000000 }, }; static bool rt298_volatile_register(struct device *dev, unsigned int reg) { switch (reg) { case 0 ... 0xff: case RT298_GET_PARAM(AC_NODE_ROOT, AC_PAR_VENDOR_ID): case RT298_GET_HP_SENSE: case RT298_GET_MIC1_SENSE: case RT298_PROC_COEF: case VERB_CMD(AC_VERB_GET_EAPD_BTLENABLE, RT298_MIC1, 0): case VERB_CMD(AC_VERB_GET_EAPD_BTLENABLE, RT298_SPK_OUT, 0): case VERB_CMD(AC_VERB_GET_EAPD_BTLENABLE, RT298_HP_OUT, 0): return true; default: return false; } } static bool rt298_readable_register(struct device *dev, unsigned int reg) { switch (reg) { case 0 ... 0xff: case RT298_GET_PARAM(AC_NODE_ROOT, AC_PAR_VENDOR_ID): case RT298_GET_HP_SENSE: case RT298_GET_MIC1_SENSE: case RT298_SET_AUDIO_POWER: case RT298_SET_HPO_POWER: case RT298_SET_SPK_POWER: case RT298_SET_DMIC1_POWER: case RT298_SPK_MUX: case RT298_HPO_MUX: case RT298_ADC0_MUX: case RT298_ADC1_MUX: case RT298_SET_MIC1: case RT298_SET_PIN_HPO: case RT298_SET_PIN_SPK: case RT298_SET_PIN_DMIC1: case RT298_SPK_EAPD: case RT298_SET_AMP_GAIN_HPO: case RT298_SET_DMIC2_DEFAULT: case RT298_DACL_GAIN: case RT298_DACR_GAIN: case RT298_ADCL_GAIN: case RT298_ADCR_GAIN: case RT298_MIC_GAIN: case RT298_SPOL_GAIN: case RT298_SPOR_GAIN: case RT298_HPOL_GAIN: case RT298_HPOR_GAIN: case RT298_F_DAC_SWITCH: case RT298_F_RECMIX_SWITCH: case RT298_REC_MIC_SWITCH: case RT298_REC_I2S_SWITCH: case RT298_REC_LINE_SWITCH: case RT298_REC_BEEP_SWITCH: case RT298_DAC_FORMAT: case RT298_ADC_FORMAT: case RT298_COEF_INDEX: case RT298_PROC_COEF: case RT298_SET_AMP_GAIN_ADC_IN1: case RT298_SET_AMP_GAIN_ADC_IN2: case RT298_SET_POWER(RT298_DAC_OUT1): case RT298_SET_POWER(RT298_DAC_OUT2): case RT298_SET_POWER(RT298_ADC_IN1): case RT298_SET_POWER(RT298_ADC_IN2): case RT298_SET_POWER(RT298_DMIC2): case RT298_SET_POWER(RT298_MIC1): case VERB_CMD(AC_VERB_GET_EAPD_BTLENABLE, RT298_MIC1, 0): case VERB_CMD(AC_VERB_GET_EAPD_BTLENABLE, RT298_SPK_OUT, 0): case VERB_CMD(AC_VERB_GET_EAPD_BTLENABLE, RT298_HP_OUT, 0): return true; default: return false; } } #ifdef CONFIG_PM static void rt298_index_sync(struct snd_soc_codec *codec) { struct rt298_priv *rt298 = snd_soc_codec_get_drvdata(codec); int i; for (i = 0; i < INDEX_CACHE_SIZE; i++) { snd_soc_write(codec, rt298->index_cache[i].reg, rt298->index_cache[i].def); } } #endif static int rt298_support_power_controls[] = { RT298_DAC_OUT1, RT298_DAC_OUT2, RT298_ADC_IN1, RT298_ADC_IN2, RT298_MIC1, RT298_DMIC1, RT298_DMIC2, RT298_SPK_OUT, RT298_HP_OUT, }; #define RT298_POWER_REG_LEN ARRAY_SIZE(rt298_support_power_controls) static int rt298_jack_detect(struct rt298_priv *rt298, bool *hp, bool *mic) { struct snd_soc_dapm_context *dapm; unsigned int val, buf; *hp = false; *mic = false; if (!rt298->codec) return -EINVAL; dapm = snd_soc_codec_get_dapm(rt298->codec); if (rt298->pdata.cbj_en) { regmap_read(rt298->regmap, RT298_GET_HP_SENSE, &buf); *hp = buf & 0x80000000; if (*hp == rt298->is_hp_in) return -1; rt298->is_hp_in = *hp; if (*hp) { /* power on HV,VERF */ regmap_update_bits(rt298->regmap, RT298_DC_GAIN, 0x200, 0x200); snd_soc_dapm_force_enable_pin(dapm, "HV"); snd_soc_dapm_force_enable_pin(dapm, "VREF"); /* power LDO1 */ snd_soc_dapm_force_enable_pin(dapm, "LDO1"); snd_soc_dapm_sync(dapm); regmap_write(rt298->regmap, RT298_SET_MIC1, 0x24); msleep(50); regmap_update_bits(rt298->regmap, RT298_CBJ_CTRL1, 0xfcc0, 0xd400); msleep(300); regmap_read(rt298->regmap, RT298_CBJ_CTRL2, &val); if (0x0070 == (val & 0x0070)) { *mic = true; } else { regmap_update_bits(rt298->regmap, RT298_CBJ_CTRL1, 0xfcc0, 0xe400); msleep(300); regmap_read(rt298->regmap, RT298_CBJ_CTRL2, &val); if (0x0070 == (val & 0x0070)) *mic = true; else *mic = false; } regmap_update_bits(rt298->regmap, RT298_DC_GAIN, 0x200, 0x0); } else { *mic = false; regmap_write(rt298->regmap, RT298_SET_MIC1, 0x20); } } else { regmap_read(rt298->regmap, RT298_GET_HP_SENSE, &buf); *hp = buf & 0x80000000; regmap_read(rt298->regmap, RT298_GET_MIC1_SENSE, &buf); *mic = buf & 0x80000000; } snd_soc_dapm_disable_pin(dapm, "HV"); snd_soc_dapm_disable_pin(dapm, "VREF"); if (!*hp) snd_soc_dapm_disable_pin(dapm, "LDO1"); snd_soc_dapm_sync(dapm); pr_debug("*hp = %d *mi<style>.highlight .hll { background-color: #ffffcc } .highlight .c { color: #888888 } /* Comment */ .highlight .err { color: #a61717; background-color: #e3d2d2 } /* Error */ .highlight .k { color: #008800; font-weight: bold } /* Keyword */ .highlight .ch { color: #888888 } /* Comment.Hashbang */ .highlight .cm { color: #888888 } /* Comment.Multiline */ .highlight .cp { color: #cc0000; font-weight: bold } /* Comment.Preproc */ .highlight .cpf { color: #888888 } /* Comment.PreprocFile */ .highlight .c1 { color: #888888 } /* Comment.Single */ .highlight .cs { color: #cc0000; font-weight: bold; background-color: #fff0f0 } /* Comment.Special */ .highlight .gd { color: #000000; background-color: #ffdddd } /* Generic.Deleted */ .highlight .ge { font-style: italic } /* Generic.Emph */ .highlight .gr { color: #aa0000 } /* Generic.Error */ .highlight .gh { color: #333333 } /* Generic.Heading */ .highlight .gi { color: #000000; background-color: #ddffdd } /* Generic.Inserted */ .highlight .go { color: #888888 } /* Generic.Output */ .highlight .gp { color: #555555 } /* Generic.Prompt */ .highlight .gs { font-weight: bold } /* Generic.Strong */ .highlight .gu { color: #666666 } /* Generic.Subheading */ .highlight .gt { color: #aa0000 } /* Generic.Traceback */ .highlight .kc { color: #008800; font-weight: bold } /* Keyword.Constant */ .highlight .kd { color: #008800; font-weight: bold } /* Keyword.Declaration */ .highlight .kn { color: #008800; font-weight: bold } /* Keyword.Namespace */ .highlight .kp { color: #008800 } /* Keyword.Pseudo */ .highlight .kr { color: #008800; font-weight: bold } /* Keyword.Reserved */ .highlight .kt { color: #888888; font-weight: bold } /* Keyword.Type */ .highlight .m { color: #0000DD; font-weight: bold } /* Literal.Number */ .highlight .s { color: #dd2200; background-color: #fff0f0 } /* Literal.String */ .highlight .na { color: #336699 } /* Name.Attribute */ .highlight .nb { color: #003388 } /* Name.Builtin */ .highlight .nc { color: #bb0066; font-weight: bold } /* Name.Class */ .highlight .no { color: #003366; font-weight: bold } /* Name.Constant */ .highlight .nd { color: #555555 } /* Name.Decorator */ .highlight .ne { color: #bb0066; font-weight: bold } /* Name.Exception */ .highlight .nf { color: #0066bb; font-weight: bold } /* Name.Function */ .highlight .nl { color: #336699; font-style: italic } /* Name.Label */ .highlight .nn { color: #bb0066; font-weight: bold } /* Name.Namespace */ .highlight .py { color: #336699; font-weight: bold } /* Name.Property */ .highlight .nt { color: #bb0066; font-weight: bold } /* Name.Tag */ .highlight .nv { color: #336699 } /* Name.Variable */ .highlight .ow { color: #008800 } /* Operator.Word */ .highlight .w { color: #bbbbbb } /* Text.Whitespace */ .highlight .mb { color: #0000DD; font-weight: bold } /* Literal.Number.Bin */ .highlight .mf { color: #0000DD; font-weight: bold } /* Literal.Number.Float */ .highlight .mh { color: #0000DD; font-weight: bold } /* Literal.Number.Hex */ .highlight .mi { color: #0000DD; font-weight: bold } /* Literal.Number.Integer */ .highlight .mo { color: #0000DD; font-weight: bold } /* Literal.Number.Oct */ .highlight .sa { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Affix */ .highlight .sb { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Backtick */ .highlight .sc { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Char */ .highlight .dl { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Delimiter */ .highlight .sd { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Doc */ .highlight .s2 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Double */ .highlight .se { color: #0044dd; background-color: #fff0f0 } /* Literal.String.Escape */ .highlight .sh { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Heredoc */ .highlight .si { color: #3333bb; background-color: #fff0f0 } /* Literal.String.Interpol */ .highlight .sx { color: #22bb22; background-color: #f0fff0 } /* Literal.String.Other */ .highlight .sr { color: #008800; background-color: #fff0ff } /* Literal.String.Regex */ .highlight .s1 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Single */ .highlight .ss { color: #aa6600; background-color: #fff0f0 } /* Literal.String.Symbol */ .highlight .bp { color: #003388 } /* Name.Builtin.Pseudo */ .highlight .fm { color: #0066bb; font-weight: bold } /* Name.Function.Magic */ .highlight .vc { color: #336699 } /* Name.Variable.Class */ .highlight .vg { color: #dd7700 } /* Name.Variable.Global */ .highlight .vi { color: #3333bb } /* Name.Variable.Instance */ .highlight .vm { color: #336699 } /* Name.Variable.Magic */ .highlight .il { color: #0000DD; font-weight: bold } /* Literal.Number.Integer.Long */</style><div class="highlight"><pre><span></span><span class="cm">/*</span> <span class="cm"> * Copyright (C) 2014 Oleksij Rempel <linux@rempel-privat.de></span> <span class="cm"> *</span> <span class="cm"> * This program is free software; you can redistribute it and/or modify</span> <span class="cm"> * it under the terms of the GNU General Public License as published by</span> <span class="cm"> * the Free Software Foundation; either version 2 of the License, or</span> <span class="cm"> * (at your option) any later version.</span> <span class="cm"> */</span> <span class="cp">#ifndef _ALPHASCALE_ASM9260_ICOLL_H</span> <span class="cp">#define _ALPHASCALE_ASM9260_ICOLL_H</span> <span class="cp">#define ASM9260_NUM_IRQS 64</span> <span class="cm">/*</span> <span class="cm"> * this device provide 4 offsets for each register:</span> <span class="cm"> * 0x0 - plain read write mode</span> <span class="cm"> * 0x4 - set mode, OR logic.</span> <span class="cm"> * 0x8 - clr mode, XOR logic.</span> <span class="cm"> * 0xc - togle mode.</span> <span class="cm"> */</span> <span class="cp">#define ASM9260_HW_ICOLL_VECTOR 0x0000</span> <span class="cm">/*</span> <span class="cm"> * bits 31:2</span> <span class="cm"> * This register presents the vector address for the interrupt currently</span> <span class="cm"> * active on the CPU IRQ input. Writing to this register notifies the</span> <span class="cm"> * interrupt collector that the interrupt service routine for the current</span> <span class="cm"> * interrupt has been entered.</span> <span class="cm"> * The exception trap should have a LDPC instruction from this address:</span> <span class="cm"> * LDPC ASM9260_HW_ICOLL_VECTOR_ADDR; IRQ exception at 0xffff0018</span> <span class="cm"> */</span> <span class="cm">/*</span> <span class="cm"> * The Interrupt Collector Level Acknowledge Register is used by software to</span> <span class="cm"> * indicate the completion of an interrupt on a specific level.</span> <span class="cm"> * This register is written at the very end of an interrupt service routine. If</span> <span class="cm"> * nesting is used then the CPU irq must be turned on before writing to this</span> <span class="cm"> * register to avoid a race condition in the CPU interrupt hardware.</span> <span class="cm"> */</span> <span class="cp">#define ASM9260_HW_ICOLL_LEVELACK 0x0010</span> <span class="cp">#define ASM9260_BM_LEVELn(nr) BIT(nr)</span> <span class="cp">#define ASM9260_HW_ICOLL_CTRL 0x0020</span> <span class="cm">/*</span> <span class="cm"> * ASM9260_BM_CTRL_SFTRST and ASM9260_BM_CTRL_CLKGATE are not available on</span> <span class="cm"> * asm9260.</span> <span class="cm"> */</span> <span class="cp">#define ASM9260_BM_CTRL_SFTRST BIT(31)</span> <span class="cp">#define ASM9260_BM_CTRL_CLKGATE BIT(30)</span> <span class="cm">/* disable interrupt level nesting */</span> <span class="cp">#define ASM9260_BM_CTRL_NO_NESTING BIT(19)</span> <span class="cm">/*</span> <span class="cm"> * Set this bit to one enable the RISC32-style read side effect associated with</span> <span class="cm"> * the vector address register. In this mode, interrupt in-service is signaled</span> <span class="cm"> * by the read of the ASM9260_HW_ICOLL_VECTOR register to acquire the interrupt</span> <span class="cm"> * vector address. Set this bit to zero for normal operation, in which the ISR</span> <span class="cm"> * signals in-service explicitly by means of a write to the</span> <span class="cm"> * ASM9260_HW_ICOLL_VECTOR register.</span> <span class="cm"> * 0 - Must Write to Vector register to go in-service.</span> <span class="cm"> * 1 - Go in-service as a read side effect</span> <span class="cm"> */</span> <span class="cp">#define ASM9260_BM_CTRL_ARM_RSE_MODE BIT(18)</span> <span class="cp">#define ASM9260_BM_CTRL_IRQ_ENABLE BIT(16)</span> <span class="cp">#define ASM9260_HW_ICOLL_STAT_OFFSET 0x0030</span> <span class="cm">/*</span> <span class="cm"> * bits 5:0</span> <span class="cm"> * Vector number of current interrupt. Multiply by 4 and add to vector base</span> <span class="cm"> * address to obtain the value in ASM9260_HW_ICOLL_VECTOR.</span> <span class="cm"> */</span> <span class="cm">/*</span> <span class="cm"> * RAW0 and RAW1 provides a read-only view of the raw interrupt request lines</span> <span class="cm"> * coming from various parts of the chip. Its purpose is to improve diagnostic</span> <span class="cm"> * observability.</span> <span class="cm"> */</span> <span class="cp">#define ASM9260_HW_ICOLL_RAW0 0x0040</span> <span class="cp">#define ASM9260_HW_ICOLL_RAW1 0x0050</span> <span class="cp">#define ASM9260_HW_ICOLL_INTERRUPT0 0x0060</span> <span class="cp">#define ASM9260_HW_ICOLL_INTERRUPTn(n) (0x0060 + ((n) >> 2) * 0x10)</span> <span class="cm">/*</span> <span class="cm"> * WARNING: Modifying the priority of an enabled interrupt may result in</span> <span class="cm"> * undefined behavior.</span> <span class="cm"> */</span> <span class="cp">#define ASM9260_BM_INT_PRIORITY_MASK 0x3</span> <span class="cp">#define ASM9260_BM_INT_ENABLE BIT(2)</span> <span class="cp">#define ASM9260_BM_INT_SOFTIRQ BIT(3)</span> <span class="cp">#define ASM9260_BM_ICOLL_INTERRUPTn_SHIFT(n) (((n) & 0x3) << 3)</span> <span class="cp">#define ASM9260_BM_ICOLL_INTERRUPTn_ENABLE(n) (1 << (2 + \</span> <span class="cp"> ASM9260_BM_ICOLL_INTERRUPTn_SHIFT(n)))</span> <span class="cp">#define ASM9260_HW_ICOLL_VBASE 0x0160</span> <span class="cm">/*</span> <span class="cm"> * bits 31:2</span> <span class="cm"> * This bitfield holds the upper 30 bits of the base address of the vector</span> <span class="cm"> * table.</span> <span class="cm"> */</span> <span class="cp">#define ASM9260_HW_ICOLL_CLEAR0 0x01d0</span> <span class="cp">#define ASM9260_HW_ICOLL_CLEAR1 0x01e0</span> <span class="cp">#define ASM9260_HW_ICOLL_CLEARn(n) (((n >> 5) * 0x10) \</span> <span class="cp"> + SET_REG)</span> <span class="cp">#define ASM9260_BM_CLEAR_BIT(n) BIT(n & 0x1f)</span> <span class="cm">/* Scratchpad */</span> <span class="cp">#define ASM9260_HW_ICOLL_UNDEF_VECTOR 0x01f0</span> <span class="cp">#endif</span> </pre></div> </code></pre></td></tr></table> </div> <!-- class=content --> <div id="lfcollabprojects-footer"> <div class="gray-diagonal"> <div class="footer-inner"> <p> © 2015 <a href="https://opnfv.org/">Open Platform for NFV Project, Inc</a>., a Linux Foundation Collaborative Project. All Rights Reserved. </p> <p> Open Platform for NFV and OPNFV are trademarks of the Open Platform for NFV Project, Inc. </p> <p> Linux Foundation is a registered trademark of The Linux Foundation. Linux is a registered <a href="http://www.linuxfoundation.org/programs/legal/trademark" title="Linux Mark Institute" >trademark</a > of Linus Torvalds. </p> <p> Please see our <a href="https://opnfv.org/about/bylaws-and-policies/terms-use" >terms of use</a >, <a href="https://opnfv.org/about/bylaws-and-policies/trademarks" >trademark policy</a >, and <a href="https://opnfv.org/about/bylaws-and-policies/privacy-policy" >privacy policy</a >. </p> </div> </div> </div> </div> <!-- id=cgit --> </body> </html> -EINVAL; } break; } if (params_channels(params) <= 16) { /* bit 3:0 Number of Channel */ val |= (params_channels(params) - 1); } else { dev_err(codec->dev, "Unsupported channels %d\n", params_channels(params)); return -EINVAL; } d_len_code = 0; switch (params_width(params)) { /* bit 6:4 Bits per Sample */ case 16: d_len_code = 0; val |= (0x1 << 4); break; case 32: d_len_code = 2; val |= (0x4 << 4); break; case 20: d_len_code = 1; val |= (0x2 << 4); break; case 24: d_len_code = 2; val |= (0x3 << 4); break; case 8: d_len_code = 3; break; default: return -EINVAL; } snd_soc_update_bits(codec, RT298_I2S_CTRL1, 0x0018, d_len_code << 3); dev_dbg(codec->dev, "format val = 0x%x\n", val); snd_soc_update_bits(codec, RT298_DAC_FORMAT, 0x407f, val); snd_soc_update_bits(codec, RT298_ADC_FORMAT, 0x407f, val); return 0; } static int rt298_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt) { struct snd_soc_codec *codec = dai->codec; switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { case SND_SOC_DAIFMT_CBM_CFM: snd_soc_update_bits(codec, RT298_I2S_CTRL1, 0x800, 0x800); break; case SND_SOC_DAIFMT_CBS_CFS: snd_soc_update_bits(codec, RT298_I2S_CTRL1, 0x800, 0x0); break; default: return -EINVAL; } switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { case SND_SOC_DAIFMT_I2S: snd_soc_update_bits(codec, RT298_I2S_CTRL1, 0x300, 0x0); break; case SND_SOC_DAIFMT_LEFT_J: snd_soc_update_bits(codec, RT298_I2S_CTRL1, 0x300, 0x1 << 8); break; case SND_SOC_DAIFMT_DSP_A: snd_soc_update_bits(codec, RT298_I2S_CTRL1, 0x300, 0x2 << 8); break; case SND_SOC_DAIFMT_DSP_B: snd_soc_update_bits(codec, RT298_I2S_CTRL1, 0x300, 0x3 << 8); break; default: return -EINVAL; } /* bit 15 Stream Type 0:PCM 1:Non-PCM */ snd_soc_update_bits(codec, RT298_DAC_FORMAT, 0x8000, 0); snd_soc_update_bits(codec, RT298_ADC_FORMAT, 0x8000, 0); return 0; } static int rt298_set_dai_sysclk(struct snd_soc_dai *dai, int clk_id, unsigned int freq, int dir) { struct snd_soc_codec *codec = dai->codec; struct rt298_priv *rt298 = snd_soc_codec_get_drvdata(codec); dev_dbg(codec->dev, "%s freq=%d\n", __func__, freq); if (RT298_SCLK_S_MCLK == clk_id) { snd_soc_update_bits(codec, RT298_I2S_CTRL2, 0x0100, 0x0); snd_soc_update_bits(codec, RT298_PLL_CTRL1, 0x20, 0x20); } else { snd_soc_update_bits(codec, RT298_I2S_CTRL2, 0x0100, 0x0100); snd_soc_update_bits(codec, RT298_PLL_CTRL, 0x4, 0x4); snd_soc_update_bits(codec, RT298_PLL_CTRL1, 0x20, 0x0); } switch (freq) { case 19200000: if (RT298_SCLK_S_MCLK == clk_id) { dev_err(codec->dev, "Should not use MCLK\n"); return -EINVAL; } snd_soc_update_bits(codec, RT298_I2S_CTRL2, 0x40, 0x40); break; case 24000000: if (RT298_SCLK_S_MCLK == clk_id) { dev_err(codec->dev, "Should not use MCLK\n"); return -EINVAL; } snd_soc_update_bits(codec, RT298_I2S_CTRL2, 0x40, 0x0); break; case 12288000: case 11289600: snd_soc_update_bits(codec, RT298_I2S_CTRL2, 0x8, 0x0); snd_soc_update_bits(codec, RT298_CLK_DIV, 0xfc1e, 0x0004); break; case 24576000: case 22579200: snd_soc_update_bits(codec, RT298_I2S_CTRL2, 0x8, 0x8); snd_soc_update_bits(codec, RT298_CLK_DIV, 0xfc1e, 0x5406); break; default: dev_err(codec->dev, "Unsupported system clock\n"); return -EINVAL; } rt298->sys_clk = freq; rt298->clk_id = clk_id; return 0; } static int rt298_set_bclk_ratio(struct snd_soc_dai *dai, unsigned int ratio) { struct snd_soc_codec *codec = dai->codec; dev_dbg(codec->dev, "%s ratio=%d\n", __func__, ratio); if (50 == ratio) snd_soc_update_bits(codec, RT298_I2S_CTRL1, 0x1000, 0x1000); else snd_soc_update_bits(codec, RT298_I2S_CTRL1, 0x1000, 0x0); return 0; } static int rt298_set_bias_level(struct snd_soc_codec *codec, enum snd_soc_bias_level level) { switch (level) { case SND_SOC_BIAS_PREPARE: if (SND_SOC_BIAS_STANDBY == snd_soc_codec_get_bias_level(codec)) { snd_soc_write(codec, RT298_SET_AUDIO_POWER, AC_PWRST_D0); snd_soc_update_bits(codec, 0x0d, 0x200, 0x200); snd_soc_update_bits(codec, 0x52, 0x80, 0x0); mdelay(20); snd_soc_update_bits(codec, 0x0d, 0x200, 0x0); snd_soc_update_bits(codec, 0x52, 0x80, 0x80); } break; case SND_SOC_BIAS_ON: mdelay(30); snd_soc_update_bits(codec, RT298_CBJ_CTRL1, 0x0400, 0x0400); break; case SND_SOC_BIAS_STANDBY: snd_soc_write(codec, RT298_SET_AUDIO_POWER, AC_PWRST_D3); snd_soc_update_bits(codec, RT298_CBJ_CTRL1, 0x0400, 0x0000); break; default: break; } return 0; } static irqreturn_t rt298_irq(int irq, void *data) { struct rt298_priv *rt298 = data; bool hp = false; bool mic = false; int ret, status = 0; ret = rt298_jack_detect(rt298, &hp, &mic); /* Clear IRQ */ regmap_update_bits(rt298->regmap, RT298_IRQ_CTRL, 0x1, 0x1); if (ret == 0) { if (hp == true) status |= SND_JACK_HEADPHONE; if (mic == true) status |= SND_JACK_MICROPHONE; snd_soc_jack_report(rt298->jack, status, SND_JACK_MICROPHONE | SND_JACK_HEADPHONE); pm_wakeup_event(&rt298->i2c->dev, 300); } return IRQ_HANDLED; } static int rt298_probe(struct snd_soc_codec *codec) { struct rt298_priv *rt298 = snd_soc_codec_get_drvdata(codec); rt298->codec = codec; if (rt298->i2c->irq) { regmap_update_bits(rt298->regmap, RT298_IRQ_CTRL, 0x2, 0x2); INIT_DELAYED_WORK(&rt298->jack_detect_work, rt298_jack_detect_work); schedule_delayed_work(&rt298->jack_detect_work, msecs_to_jiffies(1250)); } return 0; } static int rt298_remove(struct snd_soc_codec *codec) { struct rt298_priv *rt298 = snd_soc_codec_get_drvdata(codec); cancel_delayed_work_sync(&rt298->jack_detect_work); return 0; } #ifdef CONFIG_PM static int rt298_suspend(struct snd_soc_codec *codec) { struct rt298_priv *rt298 = snd_soc_codec_get_drvdata(codec); rt298->is_hp_in = -1; regcache_cache_only(rt298->regmap, true); regcache_mark_dirty(rt298->regmap); return 0; } static int rt298_resume(struct snd_soc_codec *codec) { struct rt298_priv *rt298 = snd_soc_codec_get_drvdata(codec); regcache_cache_only(rt298->regmap, false); rt298_index_sync(codec); regcache_sync(rt298->regmap); return 0; } #else #define rt298_suspend NULL #define rt298_resume NULL #endif #define RT298_STEREO_RATES (SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000) #define RT298_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \ SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S8) static const struct snd_soc_dai_ops rt298_aif_dai_ops = { .hw_params = rt298_hw_params, .set_fmt = rt298_set_dai_fmt, .set_sysclk = rt298_set_dai_sysclk, .set_bclk_ratio = rt298_set_bclk_ratio, }; static struct snd_soc_dai_driver rt298_dai[] = { { .name = "rt298-aif1", .id = RT298_AIF1, .playback = { .stream_name = "AIF1 Playback", .channels_min = 1, .channels_max = 2, .rates = RT298_STEREO_RATES, .formats = RT298_FORMATS, }, .capture = { .stream_name = "AIF1 Capture", .channels_min = 1, .channels_max = 2, .rates = RT298_STEREO_RATES, .formats = RT298_FORMATS, }, .ops = &rt298_aif_dai_ops, .symmetric_rates = 1, }, { .name = "rt298-aif2", .id = RT298_AIF2, .playback = { .stream_name = "AIF2 Playback", .channels_min = 1, .channels_max = 2, .rates = RT298_STEREO_RATES, .formats = RT298_FORMATS, }, .capture = { .stream_name = "AIF2 Capture", .channels_min = 1, .channels_max = 2, .rates = RT298_STEREO_RATES, .formats = RT298_FORMATS, }, .ops = &rt298_aif_dai_ops, .symmetric_rates = 1, }, }; static struct snd_soc_codec_driver soc_codec_dev_rt298 = { .probe = rt298_probe, .remove = rt298_remove, .suspend = rt298_suspend, .resume = rt298_resume, .set_bias_level = rt298_set_bias_level, .idle_bias_off = true, .controls = rt298_snd_controls, .num_controls = ARRAY_SIZE(rt298_snd_controls), .dapm_widgets = rt298_dapm_widgets, .num_dapm_widgets = ARRAY_SIZE(rt298_dapm_widgets), .dapm_routes = rt298_dapm_routes, .num_dapm_routes = ARRAY_SIZE(rt298_dapm_routes), }; static const struct regmap_config rt298_regmap = { .reg_bits = 32, .val_bits = 32, .max_register = 0x02370100, .volatile_reg = rt298_volatile_register, .readable_reg = rt298_readable_register, .reg_write = rl6347a_hw_write, .reg_read = rl6347a_hw_read, .cache_type = REGCACHE_RBTREE, .reg_defaults = rt298_reg, .num_reg_defaults = ARRAY_SIZE(rt298_reg), }; static const struct i2c_device_id rt298_i2c_id[] = { {"rt298", 0}, {} }; MODULE_DEVICE_TABLE(i2c, rt298_i2c_id); static const struct acpi_device_id rt298_acpi_match[] = { { "INT343A", 0 }, {}, }; MODULE_DEVICE_TABLE(acpi, rt298_acpi_match); static int rt298_i2c_probe(struct i2c_client *i2c, const struct i2c_device_id *id) { struct rt298_platform_data *pdata = dev_get_platdata(&i2c->dev); struct rt298_priv *rt298; struct device *dev = &i2c->dev; const struct acpi_device_id *acpiid; int i, ret; rt298 = devm_kzalloc(&i2c->dev, sizeof(*rt298), GFP_KERNEL); if (NULL == rt298) return -ENOMEM; rt298->regmap = devm_regmap_init(&i2c->dev, NULL, i2c, &rt298_regmap); if (IS_ERR(rt298->regmap)) { ret = PTR_ERR(rt298->regmap); dev_err(&i2c->dev, "Failed to allocate register map: %d\n", ret); return ret; } regmap_read(rt298->regmap, RT298_GET_PARAM(AC_NODE_ROOT, AC_PAR_VENDOR_ID), &ret); if (ret != RT298_VENDOR_ID) { dev_err(&i2c->dev, "Device with ID register %#x is not rt298\n", ret); return -ENODEV; } rt298->index_cache = devm_kmemdup(&i2c->dev, rt298_index_def, sizeof(rt298_index_def), GFP_KERNEL); if (!rt298->index_cache) return -ENOMEM; rt298->index_cache_size = INDEX_CACHE_SIZE; rt298->i2c = i2c; i2c_set_clientdata(i2c, rt298); /* restore codec default */ for (i = 0; i < INDEX_CACHE_SIZE; i++) regmap_write(rt298->regmap, rt298->index_cache[i].reg, rt298->index_cache[i].def); for (i = 0; i < ARRAY_SIZE(rt298_reg); i++) regmap_write(rt298->regmap, rt298_reg[i].reg, rt298_reg[i].def); if (pdata) rt298->pdata = *pdata; /* enable jack combo mode on supported devices */ acpiid = acpi_match_device(dev->driver->acpi_match_table, dev); if (acpiid) { rt298->pdata = *(struct rt298_platform_data *) acpiid->driver_data; } /* VREF Charging */ regmap_update_bits(rt298->regmap, 0x04, 0x80, 0x80); regmap_update_bits(rt298->regmap, 0x1b, 0x860, 0x860); /* Vref2 */ regmap_update_bits(rt298->regmap, 0x08, 0x20, 0x20); regmap_write(rt298->regmap, RT298_SET_AUDIO_POWER, AC_PWRST_D3); for (i = 0; i < RT298_POWER_REG_LEN; i++) regmap_write(rt298->regmap, RT298_SET_POWER(rt298_support_power_controls[i]), AC_PWRST_D1); if (!rt298->pdata.cbj_en) { regmap_write(rt298->regmap, RT298_CBJ_CTRL2, 0x0000); regmap_write(rt298->regmap, RT298_MIC1_DET_CTRL, 0x0816); regmap_update_bits(rt298->regmap, RT298_CBJ_CTRL1, 0xf000, 0xb000); } else { regmap_update_bits(rt298->regmap, RT298_CBJ_CTRL1, 0xf000, 0x5000); } mdelay(10); if (!rt298->pdata.gpio2_en) regmap_write(rt298->regmap, RT298_SET_DMIC2_DEFAULT, 0x40); else regmap_write(rt298->regmap, RT298_SET_DMIC2_DEFAULT, 0); mdelay(10); regmap_write(rt298->regmap, RT298_MISC_CTRL1, 0x0000); regmap_update_bits(rt298->regmap, RT298_WIND_FILTER_CTRL, 0x0082, 0x0082); regmap_update_bits(rt298->regmap, RT298_IRQ_CTRL, 0x2, 0x2); rt298->is_hp_in = -1; if (rt298->i2c->irq) { ret = request_threaded_irq(rt298->i2c->irq, NULL, rt298_irq, IRQF_TRIGGER_HIGH | IRQF_ONESHOT, "rt298", rt298); if (ret != 0) { dev_err(&i2c->dev, "Failed to reguest IRQ: %d\n", ret); return ret; } } ret = snd_soc_register_codec(&i2c->dev, &soc_codec_dev_rt298, rt298_dai, ARRAY_SIZE(rt298_dai)); return ret; } static int rt298_i2c_remove(struct i2c_client *i2c) { struct rt298_priv *rt298 = i2c_get_clientdata(i2c); if (i2c->irq) free_irq(i2c->irq, rt298); snd_soc_unregister_codec(&i2c->dev); return 0; } static struct i2c_driver rt298_i2c_driver = { .driver = { .name = "rt298", .acpi_match_table = ACPI_PTR(rt298_acpi_match), }, .probe = rt298_i2c_probe, .remove = rt298_i2c_remove, .id_table = rt298_i2c_id, }; module_i2c_driver(rt298_i2c_driver); MODULE_DESCRIPTION("ASoC RT298 driver"); MODULE_AUTHOR("Bard Liao <bardliao@realtek.com>"); MODULE_LICENSE("GPL");