/* set headset dac and driver power mode */ static int headset_power_mode(struct snd_soc_codec *codec, int high_perf) { int hslctl, hsrctl; int mask = TWL6040_HSDRVMODEL | TWL6040_HSDACMODEL; struct twl6040_data *priv = snd_soc_codec_get_drvdata(codec); /* Earphone doesn't support low power mode */ high_perf |= priv->earpiece_used; hslctl = twl6040_read_reg_cache(codec, TWL6040_REG_HSLCTL); hsrctl = twl6040_read_reg_cache(codec, TWL6040_REG_HSRCTL); if (high_perf) { hslctl &= ~mask; hsrctl &= ~mask; } else { hslctl |= mask; hsrctl |= mask; } twl6040_write(codec, TWL6040_REG_HSLCTL, hslctl); twl6040_write(codec, TWL6040_REG_HSRCTL, hsrctl); return 0; }
static int twl6040_mute(struct snd_soc_dai *codec_dai, int mute) { #if 0 struct snd_soc_codec *codec = codec_dai->codec; int hs_gain, hfl_gain, hfr_gain; struct snd_soc_card *card = codec_dai->card; int idx; int mute_all; hfl_gain = twl6040_read_reg_cache(codec, TWL6040_REG_HFLGAIN); hfr_gain = twl6040_read_reg_cache(codec, TWL6040_REG_HFRGAIN); hs_gain = twl6040_read_reg_cache(codec, TWL6040_REG_HSGAIN); if (mute) { mute_all = 1; for( idx = 0 ; idx < TWL6040_MUTE_DATA_MAX ; idx++ ) { if( s_mute_data[idx].dai == codec_dai ) { s_mute_data[idx].mute = mute; } if( s_mute_data[idx].dai && s_mute_data[idx].mute == 0 ) { mute_all = 0; break; } } if( mute_all ) { twl6040_i2c_write(TWL6040_REG_HFLGAIN, 0x1D); twl6040_i2c_write(TWL6040_REG_HFRGAIN, 0x1D); twl6040_i2c_write(TWL6040_REG_HSGAIN, 0xFF); } } else { mute_all = 0; for( idx = 0 ; idx < TWL6040_MUTE_DATA_MAX ; idx++ ) { if( s_mute_data[idx].dai == codec_dai ) { s_mute_data[idx].mute = mute; } if( s_mute_data[idx].dai && s_mute_data[idx].mute ) { mute_all = 1; break; } } if( mute_all == 0 ){ twl6040_write(codec, TWL6040_REG_HFLGAIN, hfl_gain); twl6040_write(codec, TWL6040_REG_HFRGAIN, hfr_gain); twl6040_write(codec, TWL6040_REG_HSGAIN, hs_gain); } } #endif return 0; }
static void twl6040_init_vio_regs(struct snd_soc_codec *codec) { u8 *cache = codec->reg_cache; int reg, i; for (i = 0; i < TWL6040_VIOREGNUM; i++) { reg = twl6040_vio_reg[i]; /* * skip read-only registers (ASICID, ASICREV, STATUS) * and registers shared among MFD children */ switch (reg) { case TWL6040_REG_ASICID: case TWL6040_REG_ASICREV: case TWL6040_REG_INTID: case TWL6040_REG_INTMR: case TWL6040_REG_NCPCTL: case TWL6040_REG_LDOCTL: case TWL6040_REG_GPOCTL: case TWL6040_REG_ACCCTL: case TWL6040_REG_STATUS: continue; default: break; } twl6040_write(codec, reg, cache[reg]); } }
static void twl6040_init_vdd_regs(struct snd_soc_codec *codec) { u8 *cache = codec->reg_cache; int reg, i; for (i = 0; i < TWL6040_VDDREGNUM; i++) { reg = twl6040_vdd_reg[i]; twl6040_write(codec, reg, cache[reg]); } }
/* set headset dac and driver power mode */ static int headset_power_mode(struct snd_soc_codec *codec, int high_perf) { int hslctl, hsrctl; int mask = TWL6040_HSDRVMODEL | TWL6040_HSDACMODEL; hslctl = twl6040_read_reg_cache(codec, TWL6040_REG_HSLCTL); hsrctl = twl6040_read_reg_cache(codec, TWL6040_REG_HSRCTL); if (high_perf) { hslctl &= ~mask; hsrctl &= ~mask; } else { hslctl |= mask; hsrctl |= mask; } twl6040_write(codec, TWL6040_REG_HSLCTL, hslctl); twl6040_write(codec, TWL6040_REG_HSRCTL, hsrctl); return 0; }
static void twl6040_init_vio_regs(struct snd_soc_codec *codec) { u8 *cache = codec->reg_cache; int reg, i; /* allow registers to be accessed by i2c */ twl6040_write(codec, TWL6040_REG_ACCCTL, cache[TWL6040_REG_ACCCTL]); for (i = 0; i < TWL6040_VIOREGNUM; i++) { reg = twl6040_vio_reg[i]; /* skip read-only registers (ASICID, ASICREV, STATUS) */ switch (reg) { case TWL6040_REG_ASICID: case TWL6040_REG_ASICREV: case TWL6040_REG_STATUS: continue; default: break; } twl6040_write(codec, reg, cache[reg]); } }
void set_hook_enable(struct snd_soc_codec *codec, int on) { u8 hkctl1; hkctl1 = twl6040_read_reg_cache(codec, TWL6040_REG_HKCTL1); if(on) hkctl1 |= TWL6040_HKEN; else hkctl1 &= ~TWL6040_HKEN; twl6040_write(codec, TWL6040_REG_HKCTL1, hkctl1); }
void hs_set_bias(struct snd_soc_codec *codec, int on) { u8 hsbias; hsbias = twl6040_read_reg_cache(codec, TWL6040_REG_AMICBCTL); if(on) hsbias |= TWL6040_HMICENA; else hsbias &= ~TWL6040_HMICENA; twl6040_write(codec, TWL6040_REG_AMICBCTL, hsbias); }
/* twl6040 codec manual power-up sequence */ static void twl6040_power_up(struct snd_soc_codec *codec) { u8 ncpctl, ldoctl, lppllctl, accctl; ncpctl = twl6040_read_reg_cache(codec, TWL6040_REG_NCPCTL); ldoctl = twl6040_read_reg_cache(codec, TWL6040_REG_LDOCTL); lppllctl = twl6040_read_reg_cache(codec, TWL6040_REG_LPPLLCTL); accctl = twl6040_read_reg_cache(codec, TWL6040_REG_ACCCTL); /* enable reference system */ ldoctl |= TWL6040_REFENA; twl6040_write(codec, TWL6040_REG_LDOCTL, ldoctl); msleep(10); /* enable internal oscillator */ ldoctl |= TWL6040_OSCENA; twl6040_write(codec, TWL6040_REG_LDOCTL, ldoctl); udelay(10); /* enable high-side ldo */ ldoctl |= TWL6040_HSLDOENA; twl6040_write(codec, TWL6040_REG_LDOCTL, ldoctl); udelay(244); /* enable negative charge pump */ ncpctl |= TWL6040_NCPENA | TWL6040_NCPOPEN; twl6040_write(codec, TWL6040_REG_NCPCTL, ncpctl); udelay(488); /* enable low-side ldo */ ldoctl |= TWL6040_LSLDOENA; twl6040_write(codec, TWL6040_REG_LDOCTL, ldoctl); udelay(244); /* enable low-power pll */ lppllctl |= TWL6040_LPLLENA; twl6040_write(codec, TWL6040_REG_LPPLLCTL, lppllctl); /* reset state machine */ accctl |= TWL6040_RESETSPLIT; twl6040_write(codec, TWL6040_REG_ACCCTL, accctl); mdelay(5); accctl &= ~TWL6040_RESETSPLIT; twl6040_write(codec, TWL6040_REG_ACCCTL, accctl); /* disable internal oscillator */ ldoctl &= ~TWL6040_OSCENA; twl6040_write(codec, TWL6040_REG_LDOCTL, ldoctl); }
static int twl6040_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) { struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_codec *codec = rtd->codec; struct twl6040_data *priv = snd_soc_codec_get_drvdata(codec); u8 lppllctl; int rate; /* nothing to do for high-perf pll, it supports only 48 kHz */ if (priv->pll == TWL6040_HPPLL_ID) return 0; lppllctl = twl6040_read_reg_cache(codec, TWL6040_REG_LPPLLCTL); rate = params_rate(params); switch (rate) { case 11250: case 22500: case 44100: case 88200: lppllctl |= TWL6040_LPLLFIN; priv->sysclk = 17640000; break; case 8000: case 16000: case 32000: case 48000: case 96000: lppllctl &= ~TWL6040_LPLLFIN; priv->sysclk = 19200000; break; default: dev_err(codec->dev, "unsupported rate %d\n", rate); return -EINVAL; } twl6040_write(codec, TWL6040_REG_LPPLLCTL, lppllctl); return 0; }
static void twl6040_init_vdd_regs(struct snd_soc_codec *codec) { u8 *cache = codec->reg_cache; int reg, i; for (i = 0; i < TWL6040_VDDREGNUM; i++) { reg = twl6040_vdd_reg[i]; /* skip vibra and pll registers */ switch (reg) { case TWL6040_REG_VIBCTLL: case TWL6040_REG_VIBDATL: case TWL6040_REG_VIBCTLR: case TWL6040_REG_VIBDATR: case TWL6040_REG_HPPLLCTL: case TWL6040_REG_LPPLLCTL: case TWL6040_REG_LPPLLDIV: continue; default: break; } twl6040_write(codec, reg, cache[reg]); } }
static int twl6040_mute(struct snd_soc_dai *codec_dai, int mute) { struct snd_soc_codec *codec = codec_dai->codec; struct twl6040_data *priv = snd_soc_codec_get_drvdata(codec); if (mute) { priv->hfl_gain = twl6040_read_reg_cache(codec, TWL6040_REG_HFLGAIN); priv->hfr_gain = twl6040_read_reg_cache(codec, TWL6040_REG_HFRGAIN); priv->hs_gain = twl6040_read_reg_cache(codec, TWL6040_REG_HSGAIN); twl6040_write(codec, TWL6040_REG_HFLGAIN, 0x1D); twl6040_write(codec, TWL6040_REG_HFRGAIN, 0x1D); twl6040_write(codec, TWL6040_REG_HSGAIN, 0xFF); } else { twl6040_write(codec, TWL6040_REG_HFLGAIN, priv->hfl_gain); twl6040_write(codec, TWL6040_REG_HFRGAIN, priv->hfr_gain); twl6040_write(codec, TWL6040_REG_HSGAIN, priv->hs_gain); } return 0; }
static int twl6040_set_dai_sysclk(struct snd_soc_dai *codec_dai, int clk_id, unsigned int freq, int dir) { struct snd_soc_codec *codec = codec_dai->codec; struct twl6040_data *priv = snd_soc_codec_get_drvdata(codec); u8 hppllctl, lppllctl; hppllctl = twl6040_read_reg_cache(codec, TWL6040_REG_HPPLLCTL); lppllctl = twl6040_read_reg_cache(codec, TWL6040_REG_LPPLLCTL); switch (clk_id) { case TWL6040_SYSCLK_SEL_LPPLL: switch (freq) { case 32768: /* headset dac and driver must be in low-power mode */ headset_power_mode(codec, 0); /* clk32k input requires low-power pll */ lppllctl |= TWL6040_LPLLENA; twl6040_write(codec, TWL6040_REG_LPPLLCTL, lppllctl); mdelay(5); lppllctl &= ~TWL6040_HPLLSEL; twl6040_write(codec, TWL6040_REG_LPPLLCTL, lppllctl); hppllctl &= ~TWL6040_HPLLENA; twl6040_write(codec, TWL6040_REG_HPPLLCTL, hppllctl); break; default: dev_err(codec->dev, "unknown mclk freq %d\n", freq); return -EINVAL; } /* lppll divider */ switch (priv->sysclk) { case 17640000: lppllctl |= TWL6040_LPLLFIN; break; case 19200000: lppllctl &= ~TWL6040_LPLLFIN; break; default: /* sysclk not yet configured */ lppllctl &= ~TWL6040_LPLLFIN; priv->sysclk = 19200000; break; } twl6040_write(codec, TWL6040_REG_LPPLLCTL, lppllctl); priv->pll = TWL6040_LPPLL_ID; priv->sysclk_constraints = &lp_constraints; break; case TWL6040_SYSCLK_SEL_HPPLL: hppllctl &= ~TWL6040_MCLK_MSK; switch (freq) { case 12000000: /* mclk input, pll enabled */ hppllctl |= TWL6040_MCLK_12000KHZ | TWL6040_HPLLSQRBP | TWL6040_HPLLENA; break; case 19200000: /* mclk input, pll disabled */ hppllctl |= TWL6040_MCLK_19200KHZ | TWL6040_HPLLSQRENA | TWL6040_HPLLBP; break; case 26000000: /* mclk input, pll enabled */ hppllctl |= TWL6040_MCLK_26000KHZ | TWL6040_HPLLSQRBP | TWL6040_HPLLENA; break; case 38400000: /* clk slicer, pll disabled */ hppllctl |= TWL6040_MCLK_38400KHZ | TWL6040_HPLLSQRENA | TWL6040_HPLLBP; break; default: dev_err(codec->dev, "unknown mclk freq %d\n", freq); return -EINVAL; } /* headset dac and driver must be in high-performance mode */ headset_power_mode(codec, 1); twl6040_write(codec, TWL6040_REG_HPPLLCTL, hppllctl); udelay(500); lppllctl |= TWL6040_HPLLSEL; twl6040_write(codec, TWL6040_REG_LPPLLCTL, lppllctl); lppllctl &= ~TWL6040_LPLLENA; twl6040_write(codec, TWL6040_REG_LPPLLCTL, lppllctl); /* high-performance pll can provide only 19.2 MHz */ priv->pll = TWL6040_HPPLL_ID; priv->sysclk = 19200000; priv->sysclk_constraints = &hp_constraints; break; default: dev_err(codec->dev, "unknown clk_id %d\n", clk_id); return -EINVAL; } return 0; }
static void twl6040_hs_jack_detect_dwork(struct work_struct *dwork) { struct twl6040_data *priv; struct snd_soc_codec *codec; struct twl6040_jack_data *jack; int status = 0; priv = container_of(dwork, struct twl6040_data, hsdet_dwork.work); codec = priv->codec; jack = &priv->hs_jack; /* * Early interrupt, CODEC driver cannot report jack status * since jack is not registered yet. MACHINE driver will * register jack and report status through twl6040_hs_jack_detect */ if (jack->jack) { #if defined(CONFIG_MACH_LGE_COSMO_REV_A) if(gpio_get_value(priv->hsjack_gpio)) #else if(jack->state) #endif { u8 val = 0; if( twl6040_i2c_read(TWL6040_REG_AMICBCTL, &val) == 0 ){ if( val != twl6040_read_reg_cache(codec, TWL6040_REG_AMICBCTL) ) twl6040_i2c_write(TWL6040_REG_AMICBCTL, twl6040_read_reg_cache(codec, TWL6040_REG_AMICBCTL)); } hs_set_bias(codec, 1); set_hook_enable(codec, 1); mdelay(200); if( twl6040_i2c_read(TWL6040_REG_MICLCTL, &val) == 0 ){ if( val != twl6040_read_reg_cache(codec, TWL6040_REG_MICLCTL) ) twl6040_i2c_write(TWL6040_REG_MICLCTL, twl6040_read_reg_cache(codec, TWL6040_REG_MICLCTL)); } if( twl6040_i2c_read(TWL6040_REG_MICRCTL, &val) == 0 ){ if( val != twl6040_read_reg_cache(codec, TWL6040_REG_MICRCTL) ) twl6040_i2c_write(TWL6040_REG_MICRCTL, twl6040_read_reg_cache(codec, TWL6040_REG_MICRCTL)); } if(is_without_mic()){ set_hook_enable(codec, 0); hs_set_bias(codec, 0); jack->state = WIRED_HEADPHONE;//wired headset without MIC status = SND_JACK_HEADPHONE; } else{ if(priv->intmask & TWL6040_HOOKMSK){ priv->intmask &= ~TWL6040_HOOKMSK; twl6040_write(codec, TWL6040_REG_INTMR, priv->intmask); } status = jack->report;//SND_JACK_HEADSET } } else{ if(!(priv->intmask & TWL6040_HOOKMSK)) { priv->intmask |= TWL6040_HOOKMSK; twl6040_write(codec, TWL6040_REG_INTMR, priv->intmask); } hs_set_bias(codec, 0); status = 0; } snd_soc_jack_report(jack->jack, status, jack->report); } switch_set_state(&jack->sdev, jack->state); }
static int twl6040_probe(struct snd_soc_codec *codec) { struct twl4030_codec_audio_data *twl_codec = codec->dev->platform_data; struct twl6040_data *priv; struct twl6040_jack_data *jack; int audpwron, naudint; struct input_dev *ip_dev; #if defined(CONFIG_MACH_LGE_COSMO_REV_A) unsigned hsjack_gpio, hsjack_irq; int err; #endif int ret = 0; u8 icrev = 0, intmr = TWL6040_ALLINT_MSK; int idx; for( idx = 0 ; idx < TWL6040_MUTE_DATA_MAX ; idx++ ) { s_mute_data[idx].dai = 0; s_mute_data[idx].mute = 0; } priv = kzalloc(sizeof(struct twl6040_data), GFP_KERNEL); if (priv == NULL) return -ENOMEM; snd_soc_codec_set_drvdata(codec, priv); priv->codec = codec; priv->dl_active = 0; priv->ul_active = 0; twl6040_i2c_read(TWL6040_REG_ASICREV, &icrev); if (twl_codec && (icrev > 0)) audpwron = twl_codec->audpwron_gpio; else audpwron = -EINVAL; #if defined(CONFIG_MACH_LGE_COSMO_REV_A) if (twl_codec){ naudint = twl_codec->naudint_irq; hsjack_gpio = twl_codec->hsjack_gpio; hsjack_irq = twl_codec->hsjack_irq; } else { naudint = 0; hsjack_gpio = 0; hsjack_irq = 0; } #else if (twl_codec) naudint = twl_codec->naudint_irq; else naudint = 0; #endif priv->audpwron = audpwron; priv->naudint = naudint; #if defined(CONFIG_MACH_LGE_COSMO_REV_A) priv->hsjack_gpio = hsjack_gpio; priv->hsjack_irq = hsjack_irq; #endif init_completion(&priv->ready); /* Disable safe mode in SYS_NIRQ PAD */ // omap_writew(0x0118, 0x4A1001A0); INIT_DELAYED_WORK(&priv->hsdet_dwork, twl6040_hs_jack_detect_dwork); INIT_DELAYED_WORK(&priv->hook_work, twl6040_hs_hook_detect_work); #ifndef CONFIG_MACH_LGE_COSMOPOLITAN INIT_WORK(&priv->audint_work, twl6040_audint_work); #endif ip_dev = input_allocate_device(); if(!ip_dev){ dev_err(codec->dev, "failed to allocation hook input device"); goto switch_err; } __set_bit(EV_KEY, ip_dev->evbit); __set_bit(EV_SYN, ip_dev->evbit); __set_bit(KEY_HOOK, ip_dev->keybit); ip_dev->name = "headset_hook"; ip_dev->phys = "headset_hook/input0"; priv->hs_jack.headset_input = ip_dev; input_register_device(priv->hs_jack.headset_input); /* switch-class based headset detection */ jack = &priv->hs_jack; jack->sdev.name = "h2w"; ret = switch_dev_register(&jack->sdev); if (ret) { dev_err(codec->dev, "error registering switch device %d\n", ret); goto switch_err; } #if defined(CONFIG_MACH_LGE_COSMO_REV_A) /* GPIO request and direction set */ if(gpio_is_valid(hsjack_gpio)) { err = gpio_request(hsjack_gpio, "ear_sense"); if (err) { printk(KERN_ERR "%s: failed to request GPIO_%d\n", __func__, hsjack_gpio); goto err_hs_gpio_request; } err = gpio_direction_input(hsjack_gpio); if (err) { printk(KERN_ERR "%s: failed to set direction GPIO_%d\n", __func__, hsjack_gpio); goto err_hs_gpio_direction; } } /* IRQ request */ err = request_irq(hsjack_irq, hsjack_irq_handler, IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING, "headset_detect", codec); if (err) { printk(KERN_ERR "%s: failed to request irq (%d)\n", __func__, hsjack_irq); goto err_hs_request_irq; } #endif if (gpio_is_valid(audpwron)) { ret = gpio_request(audpwron, "audpwron"); if (ret) goto gpio1_err; ret = gpio_direction_output(audpwron, 0); if (ret) goto gpio2_err; priv->codec_powered = 0; /* enable only codec ready interrupt */ intmr &= ~(TWL6040_READYMSK | TWL6040_PLUGMSK ); priv->intmask = intmr; /* reset interrupt status to allow correct power up sequence */ twl6040_read_reg_volatile(codec, TWL6040_REG_INTID); } twl6040_write(codec, TWL6040_REG_INTMR, intmr); if (naudint) { /* audio interrupt */ ret = request_threaded_irq(naudint, NULL, twl6040_naudint_handler, IRQF_TRIGGER_LOW | IRQF_ONESHOT, "twl6040_codec", codec); if (ret) goto gpio2_err; } /* init vio registers */ twl6040_init_vio_regs(codec); /* power on device */ ret = twl6040_set_bias_level(codec, SND_SOC_BIAS_STANDBY); if (ret) goto bias_err; snd_soc_add_controls(codec, twl6040_snd_controls, ARRAY_SIZE(twl6040_snd_controls)); twl6040_add_widgets(codec); wake_lock_init(&priv->wake_lock, WAKE_LOCK_SUSPEND, "twl6040"); cdc_tcxo_set_req_int(CDC_TCXO_CLK3, 0); /* TODO: read HS jack insertion status */ return 0; bias_err: if (naudint) free_irq(naudint, codec); #if defined(CONFIG_MACH_LGE_COSMO_REV_A) err_hs_request_irq: err_hs_gpio_direction: if (gpio_is_valid(hsjack_gpio)) gpio_free(hsjack_gpio); err_hs_gpio_request: #endif gpio2_err: if (gpio_is_valid(audpwron)) gpio_free(audpwron); gpio1_err: switch_dev_unregister(&jack->sdev); switch_err: kfree(priv); return ret; }
void twl6040_hs_jack_detect(struct snd_soc_codec *codec, struct snd_soc_jack *jack, int report) { struct twl6040_data *priv = snd_soc_codec_get_drvdata(codec); int status, state; priv->hs_jack.jack = jack; priv->hs_jack.report = report; #if !defined(CONFIG_MACH_LGE_COSMO_REV_A) /* Sync status */ status = twl6040_read_reg_volatile(codec, TWL6040_REG_STATUS); #if defined(CONFIG_MACH_LGE_COSMOPOLITAN) if(status & TWL6040_PLUGCOMP) priv->hs_jack.state = HEADSET_NONE; else priv->hs_jack.state = WIRED_HEADSET; #else if(status & TWL6040_PLUGCOMP) state = report; else state = 0; #endif #endif// !CONFIG_MACH_LGE_COSMO_REV_A #if defined(CONFIG_MACH_LGE_COSMO_REV_A) if(gpio_get_value(priv->hsjack_gpio)) #else if(priv->hs_jack.state) #endif { hs_set_bias(codec, 1); set_hook_enable(codec, 1); mdelay(200); if(is_without_mic()){ set_hook_enable(codec, 0); hs_set_bias(codec, 0); state = WIRED_HEADPHONE;//wired headset without MIC status = SND_JACK_HEADPHONE; } else{ /* for wired_headset */ if(priv->intmask & TWL6040_HOOKMSK){ priv->intmask &= ~TWL6040_HOOKMSK; twl6040_write(codec, TWL6040_REG_INTMR, priv->intmask); } state = WIRED_HEADSET;//wired headset with MIC status = priv->hs_jack.report;//SND_JACK_HEADSET } } else{ /* when headset removed */ if(!(priv->intmask & TWL6040_HOOKMSK)) { priv->intmask |= TWL6040_HOOKMSK; twl6040_write(codec, TWL6040_REG_INTMR, priv->intmask); } set_hook_enable(codec, 0); hs_set_bias(codec, 0); state = HEADSET_NONE; status = 0; } switch_set_state(&priv->hs_jack.sdev, state); snd_soc_jack_report(jack, status, report); }