📄 snd-aoa-codec-onyx.c
字号:
static u8 initial_values[ARRAY_SIZE(register_map)] = { 0x80, 0x80, /* muted */ ONYX_MRST | ONYX_SRST, /* but handled specially! */ ONYX_MUTE_LEFT | ONYX_MUTE_RIGHT, 0, /* no deemphasis */ ONYX_DAC_FILTER_ALWAYS, ONYX_OUTPHASE_INVERTED, (-1 /*dB*/ + 8) & 0xF, /* line in selected, -1 dB gain*/ ONYX_ADC_HPF_ALWAYS, (1<<2), /* pcm audio */ 2, /* category: pcm coder */ 0, /* sampling frequency 44.1 kHz, clock accuracy level II */ 1 /* 24 bit depth */};/* reset registers of chip, either to initial or to previous values */static int onyx_register_init(struct onyx *onyx){ int i; u8 val; u8 regs[sizeof(initial_values)]; if (!onyx->initialised) { memcpy(regs, initial_values, sizeof(initial_values)); if (onyx_read_register(onyx, ONYX_REG_CONTROL, &val)) return -1; val &= ~ONYX_SILICONVERSION; val |= initial_values[3]; regs[3] = val; } else { for (i=0; i<sizeof(register_map); i++) regs[i] = onyx->cache[register_map[i]-FIRSTREGISTER]; } for (i=0; i<sizeof(register_map); i++) { if (onyx_write_register(onyx, register_map[i], regs[i])) return -1; } onyx->initialised = 1; return 0;}static struct transfer_info onyx_transfers[] = { /* this is first so we can skip it if no input is present... * No hardware exists with that, but it's here as an example * of what to do :) */ { /* analog input */ .formats = SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_BE | SNDRV_PCM_FMTBIT_S24_BE, .rates = SNDRV_PCM_RATE_8000_96000, .transfer_in = 1, .must_be_clock_source = 0, .tag = 0, }, { /* if analog and digital are currently off, anything should go, * so this entry describes everything we can do... */ .formats = SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_BE | SNDRV_PCM_FMTBIT_S24_BE#ifdef SNDRV_PCM_FMTBIT_COMPRESSED_16BE | SNDRV_PCM_FMTBIT_COMPRESSED_16BE#endif , .rates = SNDRV_PCM_RATE_8000_96000, .tag = 0, }, { /* analog output */ .formats = SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_BE | SNDRV_PCM_FMTBIT_S24_BE, .rates = SNDRV_PCM_RATE_8000_96000, .transfer_in = 0, .must_be_clock_source = 0, .tag = 1, }, { /* digital pcm output, also possible for analog out */ .formats = SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_BE | SNDRV_PCM_FMTBIT_S24_BE, .rates = SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000, .transfer_in = 0, .must_be_clock_source = 0, .tag = 2, },#ifdef SNDRV_PCM_FMTBIT_COMPRESSED_16BE /* Once alsa gets supports for this kind of thing we can add it... */ { /* digital compressed output */ .formats = SNDRV_PCM_FMTBIT_COMPRESSED_16BE, .rates = SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000, .tag = 2, },#endif {}};static int onyx_usable(struct codec_info_item *cii, struct transfer_info *ti, struct transfer_info *out){ u8 v; struct onyx *onyx = cii->codec_data; int spdif_enabled, analog_enabled; mutex_lock(&onyx->mutex); onyx_read_register(onyx, ONYX_REG_DIG_INFO4, &v); spdif_enabled = !!(v & ONYX_SPDIF_ENABLE); onyx_read_register(onyx, ONYX_REG_DAC_CONTROL, &v); analog_enabled = (v & (ONYX_MUTE_RIGHT|ONYX_MUTE_LEFT)) != (ONYX_MUTE_RIGHT|ONYX_MUTE_LEFT); mutex_unlock(&onyx->mutex); switch (ti->tag) { case 0: return 1; case 1: return analog_enabled; case 2: return spdif_enabled; } return 1;}static int onyx_prepare(struct codec_info_item *cii, struct bus_info *bi, struct snd_pcm_substream *substream){ u8 v; struct onyx *onyx = cii->codec_data; int err = -EBUSY; mutex_lock(&onyx->mutex);#ifdef SNDRV_PCM_FMTBIT_COMPRESSED_16BE if (substream->runtime->format == SNDRV_PCM_FMTBIT_COMPRESSED_16BE) { /* mute and lock analog output */ onyx_read_register(onyx, ONYX_REG_DAC_CONTROL, &v); if (onyx_write_register(onyx, ONYX_REG_DAC_CONTROL, v | ONYX_MUTE_RIGHT | ONYX_MUTE_LEFT)) goto out_unlock; onyx->analog_locked = 1; err = 0; goto out_unlock; }#endif switch (substream->runtime->rate) { case 32000: case 44100: case 48000: /* these rates are ok for all outputs */ /* FIXME: program spdif channel control bits here so that * userspace doesn't have to if it only plays pcm! */ err = 0; goto out_unlock; default: /* got some rate that the digital output can't do, * so disable and lock it */ onyx_read_register(cii->codec_data, ONYX_REG_DIG_INFO4, &v); if (onyx_write_register(onyx, ONYX_REG_DIG_INFO4, v & ~ONYX_SPDIF_ENABLE)) goto out_unlock; onyx->spdif_locked = 1; err = 0; goto out_unlock; } out_unlock: mutex_unlock(&onyx->mutex); return err;}static int onyx_open(struct codec_info_item *cii, struct snd_pcm_substream *substream){ struct onyx *onyx = cii->codec_data; mutex_lock(&onyx->mutex); onyx->open_count++; mutex_unlock(&onyx->mutex); return 0;}static int onyx_close(struct codec_info_item *cii, struct snd_pcm_substream *substream){ struct onyx *onyx = cii->codec_data; mutex_lock(&onyx->mutex); onyx->open_count--; if (!onyx->open_count) onyx->spdif_locked = onyx->analog_locked = 0; mutex_unlock(&onyx->mutex); return 0;}static int onyx_switch_clock(struct codec_info_item *cii, enum clock_switch what){ struct onyx *onyx = cii->codec_data; mutex_lock(&onyx->mutex); /* this *MUST* be more elaborate later... */ switch (what) { case CLOCK_SWITCH_PREPARE_SLAVE: onyx->codec.gpio->methods->all_amps_off(onyx->codec.gpio); break; case CLOCK_SWITCH_SLAVE: onyx->codec.gpio->methods->all_amps_restore(onyx->codec.gpio); break; default: /* silence warning */ break; } mutex_unlock(&onyx->mutex); return 0;}#ifdef CONFIG_PMstatic int onyx_suspend(struct codec_info_item *cii, pm_message_t state){ struct onyx *onyx = cii->codec_data; u8 v; int err = -ENXIO; mutex_lock(&onyx->mutex); if (onyx_read_register(onyx, ONYX_REG_CONTROL, &v)) goto out_unlock; onyx_write_register(onyx, ONYX_REG_CONTROL, v | ONYX_ADPSV | ONYX_DAPSV); /* Apple does a sleep here but the datasheet says to do it on resume */ err = 0; out_unlock: mutex_unlock(&onyx->mutex); return err;}static int onyx_resume(struct codec_info_item *cii){ struct onyx *onyx = cii->codec_data; u8 v; int err = -ENXIO; mutex_lock(&onyx->mutex); /* reset codec */ onyx->codec.gpio->methods->set_hw_reset(onyx->codec.gpio, 0); msleep(1); onyx->codec.gpio->methods->set_hw_reset(onyx->codec.gpio, 1); msleep(1); onyx->codec.gpio->methods->set_hw_reset(onyx->codec.gpio, 0); msleep(1); /* take codec out of suspend (if it still is after reset) */ if (onyx_read_register(onyx, ONYX_REG_CONTROL, &v)) goto out_unlock; onyx_write_register(onyx, ONYX_REG_CONTROL, v & ~(ONYX_ADPSV | ONYX_DAPSV)); /* FIXME: should divide by sample rate, but 8k is the lowest we go */ msleep(2205000/8000); /* reset all values */ onyx_register_init(onyx); err = 0; out_unlock: mutex_unlock(&onyx->mutex); return err;}#endif /* CONFIG_PM */static struct codec_info onyx_codec_info = { .transfers = onyx_transfers, .sysclock_factor = 256, .bus_factor = 64, .owner = THIS_MODULE, .usable = onyx_usable, .prepare = onyx_prepare, .open = onyx_open, .close = onyx_close, .switch_clock = onyx_switch_clock,#ifdef CONFIG_PM .suspend = onyx_suspend, .resume = onyx_resume,#endif};static int onyx_init_codec(struct aoa_codec *codec){ struct onyx *onyx = codec_to_onyx(codec); struct snd_kcontrol *ctl; struct codec_info *ci = &onyx_codec_info; u8 v; int err; if (!onyx->codec.gpio || !onyx->codec.gpio->methods) { printk(KERN_ERR PFX "gpios not assigned!!\n"); return -EINVAL; } onyx->codec.gpio->methods->set_hw_reset(onyx->codec.gpio, 0); msleep(1); onyx->codec.gpio->methods->set_hw_reset(onyx->codec.gpio, 1); msleep(1); onyx->codec.gpio->methods->set_hw_reset(onyx->codec.gpio, 0); msleep(1); if (onyx_register_init(onyx)) { printk(KERN_ERR PFX "failed to initialise onyx registers\n"); return -ENODEV; } if (aoa_snd_device_new(SNDRV_DEV_LOWLEVEL, onyx, &ops)) { printk(KERN_ERR PFX "failed to create onyx snd device!\n"); return -ENODEV; } /* nothing connected? what a joke! */ if ((onyx->codec.connected & 0xF) == 0) return -ENOTCONN; /* if no inputs are present... */ if ((onyx->codec.connected & 0xC) == 0) { if (!onyx->codec_info) onyx->codec_info = kmalloc(sizeof(struct codec_info), GFP_KERNEL); if (!onyx->codec_info) return -ENOMEM; ci = onyx->codec_info; *ci = onyx_codec_info; ci->transfers++; } /* if no outputs are present... */ if ((onyx->codec.connected & 3) == 0) { if (!onyx->codec_info) onyx->codec_info = kmalloc(sizeof(struct codec_info), GFP_KERNEL); if (!onyx->codec_info) return -ENOMEM; ci = onyx->codec_info; /* this is fine as there have to be inputs * if we end up in this part of the code */ *ci = onyx_codec_info; ci->transfers[1].formats = 0; } if (onyx->codec.soundbus_dev->attach_codec(onyx->codec.soundbus_dev, aoa_get_card(), ci, onyx)) { printk(KERN_ERR PFX "error creating onyx pcm\n"); return -ENODEV; }#define ADDCTL(n) \ do { \ ctl = snd_ctl_new1(&n, onyx); \ if (ctl) { \ ctl->id.device = \ onyx->codec.soundbus_dev->pcm->device; \ err = aoa_snd_ctl_add(ctl); \ if (err) \ goto error; \ } \ } while (0) if (onyx->codec.soundbus_dev->pcm) { /* give the user appropriate controls * depending on what inputs are connected */ if ((onyx->codec.connected & 0xC) == 0xC) ADDCTL(capture_source_control); else if (onyx->codec.connected & 4) onyx_set_capture_source(onyx, 0); else onyx_set_capture_source(onyx, 1); if (onyx->codec.connected & 0xC) ADDCTL(inputgain_control); /* depending on what output is connected, * give the user appropriate controls */ if (onyx->codec.connected & 1) { ADDCTL(volume_control); ADDCTL(mute_control); ADDCTL(ovr1_control); ADDCTL(flt0_control); ADDCTL(hpf_control); ADDCTL(dm12_control); /* spdif control defaults to off */ } if (onyx->codec.connected & 2) { ADDCTL(onyx_spdif_mask); ADDCTL(onyx_spdif_ctrl); } if ((onyx->codec.connected & 3) == 3) ADDCTL(spdif_control); /* if only S/PDIF is connected, enable it unconditionally */ if ((onyx->codec.connected & 3) == 2) { onyx_read_register(onyx, ONYX_REG_DIG_INFO4, &v); v |= ONYX_SPDIF_ENABLE; onyx_write_register(onyx, ONYX_REG_DIG_INFO4, v); } }#undef ADDCTL printk(KERN_INFO PFX "attached to onyx codec via i2c\n"); return 0; error: onyx->codec.soundbus_dev->detach_codec(onyx->codec.soundbus_dev, onyx); snd_device_free(aoa_get_card(), onyx); return err;}static void onyx_exit_codec(struct aoa_codec *codec){ struct onyx *onyx = codec_to_onyx(codec); if (!onyx->codec.soundbus_dev) { printk(KERN_ERR PFX "onyx_exit_codec called without soundbus_dev!\n"); return; } onyx->codec.soundbus_dev->detach_codec(onyx->codec.soundbus_dev, onyx);}static struct i2c_driver onyx_driver;static int onyx_create(struct i2c_adapter *adapter, struct device_node *node, int addr){ struct onyx *onyx; u8 dummy; onyx = kzalloc(sizeof(struct onyx), GFP_KERNEL); if (!onyx) return -ENOMEM; mutex_init(&onyx->mutex); onyx->i2c.driver = &onyx_driver; onyx->i2c.adapter = adapter; onyx->i2c.addr = addr & 0x7f; strlcpy(onyx->i2c.name, "onyx audio codec", I2C_NAME_SIZE); if (i2c_attach_client(&onyx->i2c)) { printk(KERN_ERR PFX "failed to attach to i2c\n"); goto fail; } /* we try to read from register ONYX_REG_CONTROL * to check if the codec is present */ if (onyx_read_register(onyx, ONYX_REG_CONTROL, &dummy) != 0) { i2c_detach_client(&onyx->i2c); printk(KERN_ERR PFX "failed to read control register\n"); goto fail; } strlcpy(onyx->codec.name, "onyx", MAX_CODEC_NAME_LEN); onyx->codec.owner = THIS_MODULE; onyx->codec.init = onyx_init_codec; onyx->codec.exit = onyx_exit_codec; onyx->codec.node = of_node_get(node); if (aoa_codec_register(&onyx->codec)) { i2c_detach_client(&onyx->i2c); goto fail; } printk(KERN_DEBUG PFX "created and attached onyx instance\n"); return 0; fail: kfree(onyx); return -EINVAL;}static int onyx_i2c_attach(struct i2c_adapter *adapter){ struct device_node *busnode, *dev = NULL; struct pmac_i2c_bus *bus; bus = pmac_i2c_adapter_to_bus(adapter); if (bus == NULL) return -ENODEV; busnode = pmac_i2c_get_bus_node(bus); while ((dev = of_get_next_child(busnode, dev)) != NULL) { if (of_device_is_compatible(dev, "pcm3052")) { const u32 *addr; printk(KERN_DEBUG PFX "found pcm3052\n"); addr = of_get_property(dev, "reg", NULL); if (!addr) return -ENODEV; return onyx_create(adapter, dev, (*addr)>>1); } } /* if that didn't work, try desperate mode for older * machines that have stuff missing from the device tree */ if (!of_device_is_compatible(busnode, "k2-i2c")) return -ENODEV; printk(KERN_DEBUG PFX "found k2-i2c, checking if onyx chip is on it\n"); /* probe both possible addresses for the onyx chip */ if (onyx_create(adapter, NULL, 0x46) == 0) return 0; return onyx_create(adapter, NULL, 0x47);}static int onyx_i2c_detach(struct i2c_client *client){ struct onyx *onyx = container_of(client, struct onyx, i2c); int err; if ((err = i2c_detach_client(client))) return err; aoa_codec_unregister(&onyx->codec); of_node_put(onyx->codec.node); if (onyx->codec_info) kfree(onyx->codec_info); kfree(onyx); return 0;}static struct i2c_driver onyx_driver = { .driver = { .name = "aoa_codec_onyx", .owner = THIS_MODULE, }, .attach_adapter = onyx_i2c_attach, .detach_client = onyx_i2c_detach,};static int __init onyx_init(void){ return i2c_add_driver(&onyx_driver);}static void __exit onyx_exit(void){ i2c_del_driver(&onyx_driver);}module_init(onyx_init);module_exit(onyx_exit);
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -