--- linux-2.6.11-rc5/sound/arm/tlv320aic23.c 1970-01-01 01:00:00.000000000 +0100 +++ linux-2.6.11-rc5-audio9/sound/arm/tlv320aic23.c 2005-03-15 12:11:16.000000000 +0000 @@ -0,0 +1,1001 @@ +/* sound/arm/tlv320aic23.c + * + * (c) 2004-2005 Simtec Electronics + * http://armlinux.simtec.co.uk/ + * Ben Dooks + * + * TLV320AIC23 Audio codec driver + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "tlv320aic23.h" +#include "tlv320aic23-hw.h" + +#if 1 +#define DBG(x...) do { printk(KERN_DEBUG x); } while(0) +#else +#define DBG(x...) +#endif + + +#define TLV_MAXRATES (15) + +struct tlv320aic23 { + snd_card_t *card; + struct tlv320aic23_cfg *cfg; + struct tlv320aic23_rates *rates; + struct tlv320aic23_hw *hw; + struct semaphore lock; + + /* tlv320aic23 state */ + + unsigned char active; + unsigned short regs[0xf]; + + unsigned int rate_list_data[TLV_MAXRATES]; + snd_pcm_hw_constraint_list_t rate_list; +}; + +/* sample rates */ + +struct tlv320aic23_rate { + unsigned int adc; + unsigned int dac; + unsigned int sr; +}; + +struct tlv320aic23_rates { + unsigned int base; + unsigned int nr_rates; + struct tlv320aic23_rate *rates; +}; + +/* sample rate tables */ + +static struct tlv320aic23_rate rate_usb[] = { + { + .adc = 96000, + .dac = 96000, + .sr = (7 << 2), + }, { + .adc = 88200, + .dac = 88200, + .sr = (15 << 2) | TLV320AIC23_SAMPLERATE_BOSR, + }, { + .adc = 48000, + .dac = 48000, + .sr = (0 << 2), + }, { + .adc = 44100, + .dac = 44100, + .sr = (8 << 2) | TLV320AIC23_SAMPLERATE_BOSR, + }, { + .adc = 32000, + .dac = 32000, + .sr = (6 << 2), + }, { + .adc = 8021, + .dac = 8021, + .sr = (11 << 2) | TLV320AIC23_SAMPLERATE_BOSR, + }, { + .adc = 8000, + .dac = 8000, + .sr = (3 << 2), + }, { + .adc = 48000, + .dac = 8000, + .sr = (1 << 2), + }, { + .adc = 44100, + .dac = 8021, + .sr = (9 << 2) | TLV320AIC23_SAMPLERATE_BOSR, + }, { + .adc = 8000, + .dac = 48000, + .sr = (2 << 2), + }, { + .adc = 8021, + .dac = 44100, + .sr = (10 << 2) | TLV320AIC23_SAMPLERATE_BOSR, + }, +}; + +static struct tlv320aic23_rate rate_11289600[] = { + { + .adc = 88200, + .dac = 88200, + .sr = (15 << 2), + }, { + .adc = 44100, + .dac = 44100, + .sr = (8 << 2), + }, { + .adc = 8021, + .dac = 8021, + .sr = (0xb << 2), + }, { + .adc = 44100, + .dac = 8021, + .sr = (9 << 2), + }, { + .adc = 8021, + .dac = 44100, + .sr = (10 << 2), + }, +}; + +static struct tlv320aic23_rate rate_18432000[] = { + { + .adc = 96000, + .dac = 96000, + .sr = (7 << 2) | TLV320AIC23_SAMPLERATE_BOSR, + }, { + .adc = 48000, + .dac = 48000, + .sr = (0 << 2) | TLV320AIC23_SAMPLERATE_BOSR, + }, { + .adc = 32000, + .dac = 32000, + .sr = (6 << 2) | TLV320AIC23_SAMPLERATE_BOSR, + }, { + .adc = 8000, + .dac = 8000, + .sr = (3 << 2) | TLV320AIC23_SAMPLERATE_BOSR, + }, { + .adc = 48000, + .dac = 8000, + .sr = (1 << 2) | TLV320AIC23_SAMPLERATE_BOSR, + }, { + .adc = 8000, + .dac = 48000, + .sr = (6 << 2) | TLV320AIC23_SAMPLERATE_BOSR, + }, +}; + +static struct tlv320aic23_rate rate_12288000[] = { + { + .adc = 96000, + .dac = 96000, + .sr = (7 << 2), + }, { + .adc = 48000, + .dac = 48000, + .sr = 0, + }, { + .adc = 32000, + .dac = 32000, + .sr = (6 << 2), + }, { + .adc = 8000, + .dac = 8000, + .sr = (3 << 2), + }, { + .adc = 48000, + .dac = 8000, + .sr = (1 << 2), + }, { + .adc = 8000, + .dac = 48000, + .sr = (2 << 2), + }, +}; + + +static struct tlv320aic23_rates rates[] = { + { + .base = 12000000, + .rates = rate_usb, + .nr_rates = ARRAY_SIZE(rate_usb), + }, { + .base = 12288000, + .rates = rate_12288000, + .nr_rates = ARRAY_SIZE(rate_12288000), + }, { + .base = 11289600, + .rates = rate_11289600, + .nr_rates = ARRAY_SIZE(rate_11289600), + }, { + .base = 18432000, + .rates = rate_18432000, + .nr_rates = ARRAY_SIZE(rate_18432000), + }, + { } +}; + +static struct tlv320aic23_rates *tlv320aic23_get_rates(struct tlv320aic23 *tlv) +{ + struct tlv320aic23_rates *ptr = rates; + + if (tlv->cfg == NULL) + return NULL; + + DBG("getting rate for %ld\n", tlv->cfg->clkrate); + + while (ptr->base != 0 && ptr->base != tlv->cfg->clkrate) + ptr++; + + return ptr; +} + +/* sound controls */ + +struct tlv320aic23_ctl { + snd_kcontrol_new_t *ctl; + const char *name; + + int def; + int reg; + int mask; + int shift; + int min; + int max; +}; + +/* tlv register accesses code */ + +static inline int tlv320aic23_wr(struct tlv320aic23 *tlv, int reg, int val) +{ + if (reg != TLV320AIC23_RESET) + tlv->regs[reg] = val; + + return (tlv->hw->wr)(tlv->hw, reg, val); +} + +static inline int tlv320aic23_wr_changed(struct tlv320aic23 *tlv, int reg, int val) +{ + if (reg == TLV320AIC23_RESET) + return tlv320aic23_wr(tlv, reg, val); + + if (tlv->regs[reg] != val) { + DBG("%s: reg %02x changed %02x -> %02x\n", __FUNCTION__, + reg, tlv->regs[reg], val); + + return tlv320aic23_wr(tlv, reg, val); + } + + return 0; +} + +/* sound control code */ + +#define kctl_to_tlvctl(kc) ((struct tlv320aic23_ctl *) (kc)->private_value) + +static int tlv320aic23_ctl_info(snd_kcontrol_t *kc, snd_ctl_elem_info_t *ei) +{ + struct tlv320aic23_ctl *ctl = kctl_to_tlvctl(kc); + + ei->count = 1; + ei->type = (ctl->min == ctl->max) ? SNDRV_CTL_ELEM_TYPE_INTEGER : + SNDRV_CTL_ELEM_TYPE_BOOLEAN; + + if (ctl->min == ctl->max) { + ei->value.integer.min = 0; + ei->value.integer.max = 1; + } else { + ei->value.integer.min = ctl->min; + ei->value.integer.max = ctl->max; + } + + return 0; +} + +static int tlv320aic23_ctl_get(snd_kcontrol_t *kc, snd_ctl_elem_value_t *ev) +{ + struct tlv320aic23 *tlv = kc->private_data; + struct tlv320aic23_ctl *ctl = kctl_to_tlvctl(kc); + unsigned int val; + + down(&tlv->lock); + val = tlv->regs[ctl->reg]; + val &= ctl->mask; + val >>= ctl->shift; + up(&tlv->lock); + + ev->value.integer.value[0] = val; + + DBG("%s: got %d from 0x%x (0x%0x)\n", __FUNCTION__, val, ctl->reg, + tlv->regs[ctl->reg]); + + return 0; +} + +static int tlv320aic23_ctl_put(snd_kcontrol_t *kc, snd_ctl_elem_value_t *ev) +{ + struct tlv320aic23 *tlv = kc->private_data; + struct tlv320aic23_ctl *ctl = kctl_to_tlvctl(kc); + unsigned int val = ev->value.integer.value[0]; + + DBG("%s: put %p, %d\n", __FUNCTION__, kc, val); + + down(&tlv->lock); + + tlv->regs[ctl->reg] &= ~ctl->mask; + tlv->regs[ctl->reg] |= val << ctl->shift; + + if (tlv->active || 1) { + tlv320aic23_wr_changed(tlv, ctl->reg, tlv->regs[ctl->reg]); + + /* update both L/R in the case of the volume controls */ + + if (ctl->reg == TLV320AIC23_LHPVOL) + tlv320aic23_wr_changed(tlv, TLV320AIC23_RHPVOL, + tlv->regs[ctl->reg]); + + if (ctl->reg == TLV320AIC23_LINPVOL) + tlv320aic23_wr_changed(tlv, TLV320AIC23_RINPVOL, + tlv->regs[ctl->reg]); + } + + up(&tlv->lock); + return 0; +} + +static snd_kcontrol_new_t tlv320aic23_ctl = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .info = tlv320aic23_ctl_info, + .get = tlv320aic23_ctl_get, + .put = tlv320aic23_ctl_put, +}; + +/* microphone input control */ + +static int tlv320aic23_ctl_micvol_get(snd_kcontrol_t *kc, snd_ctl_elem_value_t *ev) +{ + struct tlv320aic23 *tlv = kc->private_data; + struct tlv320aic23_ctl *ctl = kctl_to_tlvctl(kc); + unsigned int apath; + unsigned int val; + + down(&tlv->lock); + apath = tlv->regs[TLV320AIC23_ANALOGPATH]; + up(&tlv->lock); + + if (apath & TLV320AIC23_APATH_MICMUTE) + val = 0; + else if (apath & TLV320AIC23_APATH_MICBOOST) + val = 2; + else + val = 1; + + ev->value.integer.value[0] = val; + + DBG("%s: got %d from 0x%x (0x%0x)\n", __FUNCTION__, val, ctl->reg, + tlv->regs[ctl->reg]); + + return 0; +} + +static int tlv320aic23_ctl_micvol_put(snd_kcontrol_t *kc, snd_ctl_elem_value_t *ev) +{ + struct tlv320aic23 *tlv = kc->private_data; + unsigned int val = ev->value.integer.value[0]; + unsigned int apath; + int ret = 0; + + DBG("%s: put %p, %d\n", __FUNCTION__, kc, val); + + down(&tlv->lock); + + apath = tlv->regs[TLV320AIC23_ANALOGPATH]; + + apath &= ~(TLV320AIC23_APATH_MICMUTE|TLV320AIC23_APATH_MICBOOST); + + switch (val) { + case 0: + apath |= TLV320AIC23_APATH_MICMUTE; + break; + + case 1: + /* do nothing, mute will be off, and no boost */ + break; + + case 2: + apath |= TLV320AIC23_APATH_MICBOOST; + break; + } + + if (tlv->active || 1) + ret = tlv320aic23_wr(tlv,TLV320AIC23_ANALOGPATH, apath); + + up(&tlv->lock); + return ret; +} + +static snd_kcontrol_new_t tlv320aic23_ctl_micvol = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .info = tlv320aic23_ctl_info, + .get = tlv320aic23_ctl_micvol_get, + .put = tlv320aic23_ctl_micvol_put, +}; + +/* list of mixer controls */ + +struct tlv320aic23_ctl ctrls[] = { + { + .ctl = &tlv320aic23_ctl, + .name = "Headphone Volume", + .reg = TLV320AIC23_LHPVOL, + .def = TLV320AIC23_HPVOL_0dB, + .shift = 0, + .mask = (1<<7)-1, + .min = 0, + .max = 127, + }, { + .ctl = &tlv320aic23_ctl, + .name = "Line Capture Volume", + .reg = TLV320AIC23_LINPVOL, + .def = TLV320AIC23_INP_0dB, + .mask = (1<<7)-1, + .min = 0, + .max = 127, + }, { + .ctl = &tlv320aic23_ctl_micvol, + .name = "Mic Volume", + .def = 0, + .min = 0, + .max = 2, + }, { + .ctl = &tlv320aic23_ctl, + .name = "Mic Capture Switch", + .reg = TLV320AIC23_ANALOGPATH, + .def = 0, + .shift = 2, + .mask = TLV320AIC23_APATH_MICADC, + }, { + .ctl = &tlv320aic23_ctl, + .name = "Bypass", + .reg = TLV320AIC23_ANALOGPATH, + .def = 0, + .shift = 3, + .mask = TLV320AIC23_APATH_BYPASSEN, + }, { + .ctl = &tlv320aic23_ctl, + .name = "Sidetone Enable", + .reg = TLV320AIC23_ANALOGPATH, + .def = 0, + .shift = 5, + .mask = TLV320AIC23_APATH_SIDETONE_EN, + }, { + .ctl = &tlv320aic23_ctl, + .name = "Sidetone Attenuation", + .reg = TLV320AIC23_ANALOGPATH, + .def = 0, + .shift = 6, + .mask = TLV320AIC23_APATH_SIDETONE15dB, + .min = 0, + .max = 3, + }, { + .ctl = &tlv320aic23_ctl, + .name = "DAC Enable", + .reg = TLV320AIC23_ANALOGPATH, + .def = 1, + .shift = 4, + .mask = TLV320AIC23_APATH_DACEN, + }, +}; + +static int +tlv320aic23_add_ctrl(struct tlv320aic23 *tlv, struct tlv320aic23_ctl *ctl) +{ + snd_kcontrol_t *kctl; + int ret = -ENOMEM; + + DBG("%s: %p: ctl %p (%s)\n", __FUNCTION__, tlv, ctl, ctl->name); + + kctl = snd_ctl_new1(ctl->ctl, tlv); + if (kctl) { + strlcpy(kctl->id.name, ctl->name, sizeof(kctl->id.name)); + kctl->private_value = (unsigned long)ctl; + + // todo - ensure default value // + + ret = snd_ctl_add(tlv->card, kctl); + if (ret < 0) + snd_ctl_free_one(kctl); + } + + return ret; +} + +static int tlv320aic23_add_ctrls(struct tlv320aic23 *tlv) +{ + int ret; + int i; + + for (i = 0; i < ARRAY_SIZE(ctrls); i++) { + ret = tlv320aic23_add_ctrl(tlv, &ctrls[i]); + if (ret) + return ret; + } + + return 0; +} + +/* rate control */ + +static int tlv320aic23_rates_to_list(struct tlv320aic23_rates *rates, + snd_pcm_hw_constraint_list_t *list, + int dac) +{ + struct tlv320aic23_rate *rp; + int *ptr = list->list; + int clk; + + DBG("%s: rates=%p, list=%p, dac=%d\n", __FUNCTION__, rates, list, dac); + DBG("%s: rates->rates =%p, list->list=%p\n", __FUNCTION__, rates->rates, list->list); + + if (rates == NULL) + return -EINVAL; + + if (rates->rates == NULL) + return -EINVAL; + + rp = rates->rates; + + list->count = 0; + list->mask = 0; + + for (clk = 0; clk < TLV_MAXRATES; clk++, rp++, ptr++) { + if (clk >= rates->nr_rates) + break; + + *ptr = dac ? rp->dac : rp->adc; + list->count++; + } + + /* sort the resutls */ + + restart_sort: + for (clk = 0; clk < list->count-1; clk++) { + ptr = list->list + clk; + if (ptr[0] > ptr[1]) { + int tmp = ptr[1]; + ptr[1] = ptr[0]; + ptr[0] = tmp; + goto restart_sort; + } + } + + return 0; +} + +static int tlv320aic23_find_sr(struct tlv320aic23 *tlv, int rate) +{ + struct tlv320aic23_rate *rp = tlv->rates->rates; + int i; + + DBG("%s(%p,%d)\n", __FUNCTION__, tlv, rate); + + for (i = 0; i < tlv->rates->nr_rates; i++, rp++) { + if (rp->dac == rate) + return rp->sr; + } + + return -1; +} + + +static int tlv320aic23_reset(struct tlv320aic23 *tlv) +{ + int ret; + + ret = tlv320aic23_wr(tlv, TLV320AIC23_RESET, 0x00); + if (ret) + return ret; + + mdelay(1); + return 0; +} + +static int tlv320aic23_configure(struct tlv320aic23 *tlv) +{ + struct tlv320aic23_cfg *cfg = tlv->cfg; + int ret; + int reg; + + /* set wether we are clock master or not */ + + tlv->regs[TLV320AIC23_DIGITALFMT] &= ~TLV320AIC23_DIGITALFMT_MASTER; + + if (cfg->master) + tlv->regs[TLV320AIC23_DIGITALFMT] |= TLV320AIC23_DIGITALFMT_MASTER; + + /* configure all registers */ + + for (reg = 0; reg < TLV320AIC23_RESET; reg++) { + ret = tlv320aic23_wr(tlv, reg, tlv->regs[reg]); + } + + /* check over the clock */ + + tlv->rates = tlv320aic23_get_rates(tlv); + if (tlv->rates == NULL) { + ret = -EINVAL; + goto err; + } + + return 0; + + err: + return ret; +} + +static int tlv320aic23_free(snd_device_t *dev) +{ + struct tlv320aic23 *tlv = dev->device_data; + + if (tlv->hw && tlv->hw->release) + tlv->hw->release(tlv->hw); + + kfree(tlv); + return 0; +} + +static snd_device_ops_t tlv_ops = { + .dev_free = tlv320aic23_free, +}; + +static unsigned short tlv320aic23_defaultregs[TLV320AIC23_RESET] = { + [TLV320AIC23_LINPVOL] = TLV320AIC23_INP_SIMULTUPD, + [TLV320AIC23_RINPVOL] = TLV320AIC23_INP_SIMULTUPD, + [TLV320AIC23_LHPVOL] = TLV320AIC23_HPVOL_ZEROCROSS, + [TLV320AIC23_RHPVOL] = TLV320AIC23_HPVOL_ZEROCROSS, + [TLV320AIC23_DIGITALPATH] = TLV320AIC23_DPATH_SOFTMUTE, + [TLV320AIC23_POWERCTRL] = TLV320AIC23_PDC_DEFAULT, + [TLV320AIC23_DIGITALFMT] = 0, + [TLV320AIC23_SAMPLERATE] = 0, + [TLV320AIC23_DIGITALACT] = 1, +}; + +struct tlv320aic23 *tlv320aic23_attach(snd_card_t *card, + struct device *dev, + struct tlv320aic23_cfg *cfg) +{ + struct tlv320aic23 *tlv; + int ret = 0; + + DBG("%s: card=%p, cfg=%p\n", __FUNCTION__, card, cfg); + + tlv = kmalloc(sizeof(*tlv), GFP_KERNEL); + if (tlv == NULL) + return ERR_PTR(-ENOMEM); + + memset(tlv, 0, sizeof(*tlv)); + memcpy(&tlv->regs, tlv320aic23_defaultregs, sizeof(tlv->regs)); + init_MUTEX(&tlv->lock); + + tlv->card = card; + tlv->cfg = cfg; + tlv->hw = dev->platform_data; + + if (tlv->hw == NULL) { + dev_err(dev, "no platform data\n"); + ret = -ENOENT; + goto exit_err; + } + + if (tlv->hw->claim) { + ret = (tlv->hw->claim)(tlv->hw); + + if (ret) + goto exit_err; + } + + /* create a new sound device and attach controls */ + + ret = snd_device_new(card, SNDRV_DEV_LOWLEVEL, tlv, &tlv_ops); + if (ret) + goto exit_err; + + /* initialise variables */ + + tlv->rate_list.list = tlv->rate_list_data; + + /* configure the device */ + + tlv320aic23_reset(tlv); + tlv320aic23_configure(tlv); + + /* attach the mixer controls */ + + ret = tlv320aic23_add_ctrls(tlv); + if (ret) + goto exit_err; + + /* ok, return our new client */ + + return tlv; + + exit_err: + return ERR_PTR(ret); +} + +EXPORT_SYMBOL(tlv320aic23_attach); + +void tlv320aic23_detach(struct tlv320aic23 *mixer) +{ + /* nothing to do here */ +} + +EXPORT_SYMBOL(tlv320aic23_detach); + +/* routines to deal with the sound system */ + +int tlv320aic23_startup(struct tlv320aic23 *tlv) +{ + int ret = 0; + int tmp; + + /* power up the codec dac section */ + + tmp = tlv->regs[TLV320AIC23_POWERCTRL]; + tmp &= ~TLV320AIC23_PDC_DAC; + tmp &= ~TLV320AIC23_PDC_OSC; + tmp &= ~TLV320AIC23_PDC_CLOCK; + + ret = tlv320aic23_wr(tlv, TLV320AIC23_POWERCTRL, tmp); + if (ret < 0) + goto err; + + err: + return ret; +} + +EXPORT_SYMBOL(tlv320aic23_startup); + +int tlv320aic23_shutdown(struct tlv320aic23 *tlv) +{ + int ret; + int tmp; + + DBG("%s: tlv=%p\n", __FUNCTION__, tlv); + + /* power down the codec dac section */ + + tmp = tlv->regs[TLV320AIC23_POWERCTRL]; + tmp |= TLV320AIC23_PDC_DAC; + tmp |= TLV320AIC23_PDC_OSC; + tmp |= TLV320AIC23_PDC_CLOCK; + + ret = tlv320aic23_wr(tlv, TLV320AIC23_POWERCTRL, tmp); + if (ret < 0) + goto err; + + return 0; + + err: + return ret; +} + +EXPORT_SYMBOL(tlv320aic23_shutdown); + +int tlv320aic23_open(struct tlv320aic23 *tlv, snd_pcm_substream_t *substream) +{ + snd_pcm_runtime_t *runtime = substream->runtime; + snd_pcm_hardware_t *hw = &runtime->hw; + int tmp; + + DBG("%s: tlv=%p, substream=%p\n", __FUNCTION__, tlv, substream); + DBG("%s: runtime=%p, hw=%p\n", __FUNCTION__, runtime, hw); + + if (tlv == NULL || substream == NULL || hw == NULL) + return -EINVAL; + + hw->channels_min = 2; + hw->channels_max = 2; + hw->rates = SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_KNOT; + hw->formats &= (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE); + + /* constrict the rates */ + + tlv320aic23_rates_to_list(tlv->rates, &tlv->rate_list, 0); + + snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, + &tlv->rate_list); + + /* ensure the appropriate hardware is powered up */ + + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { + /* bring power to the ADC and inputs */ + + tmp = tlv->regs[TLV320AIC23_POWERCTRL]; + tmp &= ~TLV320AIC23_PDC_ADC; + tmp &= ~TLV320AIC23_PDC_MIC; + tmp &= ~TLV320AIC23_PDC_LINE; + tlv320aic23_wr_changed(tlv, TLV320AIC23_POWERCTRL, tmp); + } else { + /* mute the system whilst we power up the dac/output */ + + tmp = tlv->regs[TLV320AIC23_DIGITALPATH]; + tmp |= TLV320AIC23_DPATH_SOFTMUTE; + tlv320aic23_wr(tlv, TLV320AIC23_DIGITALPATH, tmp); + + /* bring power to the DAC and Output stages */ + + tmp = tlv->regs[TLV320AIC23_POWERCTRL]; + tmp &= ~TLV320AIC23_PDC_OUTPUT; + tmp &= ~TLV320AIC23_PDC_DAC; + tlv320aic23_wr_changed(tlv, TLV320AIC23_POWERCTRL, tmp); + } + + return 0; +} + +EXPORT_SYMBOL(tlv320aic23_open); + +/* tlv320aic23_close + * + * called to close down an stream, so turn off power and make sure that + * anything else is stopped +*/ + +int tlv320aic23_close(struct tlv320aic23 *tlv, snd_pcm_substream_t *substream) +{ + int tmp; + + DBG("%s: tlv=%p, substream=%p\n", __FUNCTION__, tlv, substream); + + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { + /* remove power from the ADC and inputs */ + + tmp = tlv->regs[TLV320AIC23_POWERCTRL]; + tmp |= TLV320AIC23_PDC_ADC; + tmp |= TLV320AIC23_PDC_MIC; + tmp |= TLV320AIC23_PDC_LINE; + tlv320aic23_wr_changed(tlv, TLV320AIC23_POWERCTRL, tmp); + } else { + /* mute output */ + + tmp = tlv->regs[TLV320AIC23_DIGITALPATH]; + tmp |= TLV320AIC23_DPATH_SOFTMUTE; + tlv320aic23_wr(tlv, TLV320AIC23_DIGITALPATH, tmp); + + /* remove power from the DAC */ + + tmp = tlv->regs[TLV320AIC23_POWERCTRL]; + tmp |= TLV320AIC23_PDC_DAC; + tlv320aic23_wr_changed(tlv, TLV320AIC23_POWERCTRL, tmp); + } + + + return 0; +} + +EXPORT_SYMBOL(tlv320aic23_close); + +static int tlv320aic23_prepare_capture(struct tlv320aic23 *tlv) +{ + /* ADC and line should already be powered up */ + + return 0; +} + +static int tlv320aic23_prepare_playback(struct tlv320aic23 *tlv) +{ + int tmp; + + tmp = tlv->regs[TLV320AIC23_POWERCTRL]; + tmp &= ~TLV320AIC23_PDC_OUTPUT; + tmp &= ~TLV320AIC23_PDC_DAC; + tlv320aic23_wr_changed(tlv, TLV320AIC23_POWERCTRL, tmp); + + /* ensure system not muted */ + + tmp = tlv->regs[TLV320AIC23_DIGITALPATH]; + tmp &= ~TLV320AIC23_DPATH_SOFTMUTE; + tlv320aic23_wr(tlv, TLV320AIC23_DIGITALPATH, tmp); + + /* update the analogue path */ + + tmp = tlv->regs[TLV320AIC23_ANALOGPATH]; + tmp |= TLV320AIC23_APATH_DACEN; + tlv320aic23_wr_changed(tlv, TLV320AIC23_ANALOGPATH, tmp); + + /* ok, let's try a reset */ + + if (0) { + int reg; + int ret; + + tlv320aic23_reset(tlv); + + /* configure all registers */ + + for (reg = 0; reg < TLV320AIC23_RESET; reg++) { + ret = tlv320aic23_wr(tlv, reg, tlv->regs[reg]); + } + } + + /* try and reset the dac by power-cycling */ + + if (1) { + tmp = tlv->regs[TLV320AIC23_POWERCTRL]; + tlv320aic23_wr(tlv, TLV320AIC23_POWERCTRL, + tmp|TLV320AIC23_PDC_DAC); + + udelay(1); + + tmp &= ~TLV320AIC23_PDC_DAC; + tlv320aic23_wr(tlv, TLV320AIC23_POWERCTRL, tmp); + } + + return 0; +} + + +int tlv320aic23_prepare(struct tlv320aic23 *tlv, + snd_pcm_substream_t *substream, + snd_pcm_runtime_t *runtime) +{ + int sr = tlv320aic23_find_sr(tlv, runtime->rate); + int tmp; + int ret = 0; + + /* update sample rate */ + + tmp = tlv->regs[TLV320AIC23_SAMPLERATE]; + tmp &= ~TLV320AIC23_SAMPLERATE_BOSR; + tmp &= ~TLV320AIC23_SAMPLERATE_USB; + tmp &= ~TLV320AIC23_SAMPLERATE_MASK; + + if (tlv->cfg->clkrate == 12000000) + tmp |= TLV320AIC23_SAMPLERATE_USB; + + if (sr == -1) { + printk(KERN_ERR "cannot get rate for %d\n", runtime->rate); + return -EINVAL; + } + + tlv320aic23_wr_changed(tlv, TLV320AIC23_SAMPLERATE, tmp | sr); + + /* update format */ + + tmp = tlv->regs[TLV320AIC23_DIGITALFMT]; + tmp &= ~TLV320AIC23_DIGITALFMT_BITMASK; + tmp |= TLV320AIC23_DIGITALFMT_I2S; // todo - sortout correct interface + + switch (runtime->format) { + case SNDRV_PCM_FORMAT_S16_LE: + tmp |= TLV320AIC23_DIGITALFMT_16BIT; + break; + + default: + printk(KERN_ERR "unknown data format\n"); + return -EINVAL; + } + + tlv320aic23_wr_changed(tlv, TLV320AIC23_DIGITALFMT, tmp); + + /* ensure that the system is powered up */ + + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + ret = tlv320aic23_prepare_capture(tlv); + else + ret = tlv320aic23_prepare_playback(tlv); + + return ret; +} + +EXPORT_SYMBOL(tlv320aic23_prepare); + --- linux-2.6.11-rc5/sound/arm/tlv320aic23.h 1970-01-01 01:00:00.000000000 +0100 +++ linux-2.6.11-rc5-audio9/sound/arm/tlv320aic23.h 2005-03-15 10:10:26.000000000 +0000 @@ -0,0 +1,35 @@ +/* sound/arm/tlv320aic23.h + * + * (c) 2004-2005 Simtec Electronics + * http://armlinux.simtec.co.uk/ + * Ben Dooks + * + * Texas Instruments TLV320AIC23 codec driver + * + * 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. + * +*/ + +struct tlv320aic23; + +struct tlv320aic23_cfg { + unsigned long clkrate; + unsigned char master; + unsigned char split_rates; +}; + +extern struct tlv320aic23 *tlv320aic23_attach(snd_card_t *card, + struct device *dev, + struct tlv320aic23_cfg *cfg); + +extern void tlv320aic23_detach(struct tlv320aic23 *tlv); +extern int tlv320aic23_startup(struct tlv320aic23 *tlv); +extern int tlv320aic23_open(struct tlv320aic23 *, snd_pcm_substream_t *); +extern int tlv320aic23_close(struct tlv320aic23 *, snd_pcm_substream_t *); + +extern int tlv320aic23_prepare(struct tlv320aic23 *tlv, + snd_pcm_substream_t *substream, + snd_pcm_runtime_t *runtime); + --- linux-2.6.11-rc5/sound/arm/tlv320aic23-i2c.c 1970-01-01 01:00:00.000000000 +0100 +++ linux-2.6.11-rc5-audio9/sound/arm/tlv320aic23-i2c.c 2005-03-15 12:11:27.000000000 +0000 @@ -0,0 +1,180 @@ +/* sound/arm/tlv320aic23-i2c.c + * + * (c) 2004-2005 Simtec Electronics + * http://armlinux.simtec.co.uk/ + * Ben Dooks + * + * TLV320AIC23 Audio codec driver - I2C interface + * + * 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 +#include +#include +#include +#include +#include +#include + +#include "tlv320aic23-hw.h" + +static unsigned short normal_i2c[] = { 0x1A, I2C_CLIENT_END }; +static unsigned int normal_isa[] = { I2C_CLIENT_ISA_END }; +static struct i2c_force_data forces[] = { { NULL }, 0 }; + +static struct i2c_address_data addr_data = { + .normal_i2c = normal_i2c, + .normal_isa = normal_isa, + .forces = forces, +}; + +struct tlv320aic23_data { + struct i2c_client client; + struct platform_device my_dev; + struct tlv320aic23_hw hw; +}; + +static struct i2c_driver tlv320aic23_driver; + +static int tlv320aic23_id; + +static inline struct i2c_client *hw_to_client(struct tlv320aic23_hw *tlv) +{ + struct tlv320aic23_data *dp; + + dp = container_of(tlv, struct tlv320aic23_data, hw); + return &dp->client; +} + +static int tlv320aic23_i2c_claim(struct tlv320aic23_hw *tlv) +{ + struct i2c_client *client = hw_to_client(tlv); + return i2c_use_client(client); +} + +static int tlv320aic23_i2c_release(struct tlv320aic23_hw *tlv) +{ + struct i2c_client *client = hw_to_client(tlv); + return i2c_release_client(client); +} + +static int tlv320aic23_wr_i2c(struct tlv320aic23_hw *tlv, int reg, int val) +{ + struct i2c_client *client = hw_to_client(tlv); + unsigned char data[2]; + struct i2c_msg msg[1]; + int done; + + msg[0].addr = client->addr; + msg[0].flags = 0; + msg[0].len = 2; + msg[0].buf = data; + + data[0] = reg << 1; + data[1] = val; + + if (val & (1<<8)) + data[0] |= 1; + + done = i2c_transfer(client->adapter, msg, 1); + + printk(KERN_DEBUG "%s: (%02x,%02x) wr %02x,%02x ret %d\n", + __FUNCTION__, reg, val, data[0], data[1], done); + + return done == 1 ? 0 : -EIO; +} + + +static int tlv320aic23_detect(struct i2c_adapter *adap, int addr, int kind) +{ + struct tlv320aic23_data *tlv; + int err; + + tlv = kmalloc(sizeof(*tlv), GFP_KERNEL); + if (tlv == NULL) { + err = -ENOMEM; + goto exit_err; + } + + memset(tlv, 0, sizeof(*tlv)); + + i2c_set_clientdata(&tlv->client, tlv); + tlv->client.addr = addr; + tlv->client.adapter = adap; + tlv->client.driver = &tlv320aic23_driver; + tlv->client.id = tlv320aic23_id++; + + tlv->hw.dev = &adap->dev; + tlv->hw.owner = THIS_MODULE; + tlv->hw.wr = tlv320aic23_wr_i2c; + tlv->hw.claim = tlv320aic23_i2c_claim; + tlv->hw.release = tlv320aic23_i2c_release; + + tlv->my_dev.dev.platform_data = &tlv->hw; + tlv->my_dev.dev.parent = &adap->dev; + tlv->my_dev.name = "tlv320aic23"; + + strlcpy(tlv->client.name, "tlv320aic23", I2C_NAME_SIZE); + + err = i2c_attach_client(&tlv->client); + if (err) + goto exit_err; + + /* registered ok */ + + platform_device_register(&tlv->my_dev); + + return 0; + + exit_err: + return err; +} + +static int tlv320aic23_attach_adapter(struct i2c_adapter *adapter) +{ + return i2c_detect(adapter, &addr_data, tlv320aic23_detect); +} + +static int tlv320aic23_detach_client(struct i2c_client *client) +{ + int err; + + if ((err = i2c_detach_client(client))) { + dev_err(&client->dev, "Client deregistration failed, " + "client not detached.\n"); + return err; + } + + kfree(i2c_get_clientdata(client)); + return 0; +} + + +static struct i2c_driver tlv320aic23_driver = { + .owner = THIS_MODULE, + .name = "tlv320aic23", + .flags = I2C_DF_NOTIFY, + .attach_adapter = tlv320aic23_attach_adapter, + .detach_client = tlv320aic23_detach_client, +}; + + +static int __init tlv320aic23_init(void) +{ + return i2c_add_driver(&tlv320aic23_driver); +} + +static void __exit tlv320aic23_exit(void) +{ + i2c_del_driver(&tlv320aic23_driver); +} + +module_init(tlv320aic23_init); +module_exit(tlv320aic23_exit); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("TLV320AIC23 I2C Audio driver"); +MODULE_AUTHOR("Ben Dooks "); --- linux-2.6.11-rc5/sound/arm/tlv320aic23-hw.h 1970-01-01 01:00:00.000000000 +0100 +++ linux-2.6.11-rc5-audio9/sound/arm/tlv320aic23-hw.h 2005-03-15 10:10:31.000000000 +0000 @@ -0,0 +1,29 @@ +/* sound/arm/tlv320aic23-hw.h + * + * (c) 2004-2005 Simtec Electronics + * http://armlinux.simtec.co.uk/ + * Ben Dooks + * + * Texas Instruments TLV320AIC23 codec driver + * + * 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. + * +*/ + +/* struct tlv320aic23_hw + * + * exported by the specific hardware driver +*/ + +struct tlv320aic23_hw; + +struct tlv320aic23_hw { + struct module *owner; + struct device *dev; + + int (*claim)(struct tlv320aic23_hw *); + int (*release)(struct tlv320aic23_hw *); + int (*wr)(struct tlv320aic23_hw *hw, int reg, int val); +};