subject: aoa topaz --- sound/aoa/aoa-gpio.h | 1 sound/aoa/codecs/Kconfig | 13 + sound/aoa/codecs/Makefile | 3 sound/aoa/codecs/topaz.c | 435 ++++++++++++++++++++++++++++++++++++++++++++++ sound/aoa/codecs/topaz.h | 116 ++++++++++++ sound/aoa/core/gpio-pmf.c | 13 + 6 files changed, 581 insertions(+) --- wireless-testing.orig/sound/aoa/codecs/Kconfig 2009-11-03 10:21:06.000000000 +0100 +++ wireless-testing/sound/aoa/codecs/Kconfig 2009-11-11 20:20:07.000000000 +0100 @@ -30,3 +30,16 @@ config SND_AOA_TOONIE This option enables support for the toonie codec found in the Mac Mini. If you have a Mac Mini and want to hear sound, select this option. + +config SND_AOA_TOPAZ + tristate "support Topaz chip" + help + This option enables support for the topaz codecs + found in some PowerMacs and used for digital in- + and output. It supports the CS8406 and CS8420 + chips from Cirrus Logic, but currently for output + only. + + If you have a machine with digital output that is + not driven by an Onyx chip (see above), select + this option. --- wireless-testing.orig/sound/aoa/codecs/Makefile 2009-11-03 10:21:06.000000000 +0100 +++ wireless-testing/sound/aoa/codecs/Makefile 2009-11-11 20:20:07.000000000 +0100 @@ -5,3 +5,6 @@ snd-aoa-codec-toonie-objs := toonie.o obj-$(CONFIG_SND_AOA_ONYX) += snd-aoa-codec-onyx.o obj-$(CONFIG_SND_AOA_TAS) += snd-aoa-codec-tas.o obj-$(CONFIG_SND_AOA_TOONIE) += snd-aoa-codec-toonie.o + +snd-aoa-codec-topaz-objs += topaz.o +obj-$(CONFIG_SND_AOA_TOPAZ) += snd-aoa-codec-topaz.o --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ wireless-testing/sound/aoa/codecs/topaz.c 2009-11-11 20:20:07.000000000 +0100 @@ -0,0 +1,435 @@ +/* + * Apple Onboard Audio driver for Topaz codec + * + * Copyright 2006, 2008 Johannes Berg + * + * GPL v2, can be found in COPYING. + * + * + * This is a driver for the CS8406 and CS8420 codec chips made + * by Cirrus Logic (codenamed Topaz) that is present in some + * Apple hardware (with digital output). The CS8416 nor the + * digital input of the CS8420 is currently supported. + * + * The Topaz codecs have the following connections: + * * digital output (CS8406, CS8420) + * * digital input (CS8416, CS8420) + * + * NOTE: This driver assumes that there's at most one chip to be + * used with one alsa card, in form of creating all kinds + * of mixer elements without regard for their existence. + * But snd-aoa assumes that there's at most one card, so + * this means you can only have one topaz on a system. This + * should probably be fixed by changing the assumption of + * having just a single card on a system, and making the + * 'card' pointer accessible to anyone who needs it instead + * of hiding it in the aoa_snd_* functions... + * + */ +#include +#include +MODULE_AUTHOR("Johannes Berg "); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("cs8406/20 (topaz) codec driver for snd-aoa"); + +#include "topaz.h" +#include "../aoa.h" +#include "../soundbus/soundbus.h" + + +#define PFX "snd-aoa-codec-topaz: " + +struct topaz { + struct i2c_client i2c; + struct aoa_codec codec; + int open_count; + struct codec_info *codec_info; + + bool initialised; + + /* mutex serializes concurrent access to the device + * and this structure. + */ + struct mutex mutex; +}; +#define codec_to_topaz(c) container_of(c, struct topaz, codec) + +/* both return 0 if all ok, else on error */ +static int topaz_read_register(struct topaz *topaz, u8 reg, u8 *value) +{ + s32 v; + + v = i2c_smbus_read_byte_data(&topaz->i2c, reg); + if (v < 0) + return -1; + *value = (u8)v; + return 0; +} + +static int topaz_write_register(struct topaz *topaz, u8 reg, u8 value) +{ + return i2c_smbus_write_byte_data(&topaz->i2c, reg, value); +} + +/* alsa stuff */ + +static int topaz_dev_register(struct snd_device *dev) +{ + return 0; +} + +static struct snd_device_ops ops = { + .dev_register = topaz_dev_register, +}; + +/* reset registers of chip, either to initial or to previous values */ +static int topaz_register_init(struct topaz *topaz) +{ + if (!topaz->initialised) { + /* XXX: just some default setups per datasheet? */ + } + + topaz->initialised = true; + return 0; +} + +static struct transfer_info topaz_transfers[] = { + { + /* digital pcm output, also possible for analog out */ + .formats = SNDRV_PCM_FMTBIT_S16_BE | + SNDRV_PCM_FMTBIT_S24_BE, + .rates = SNDRV_PCM_RATE_32000 | + SNDRV_PCM_RATE_44100 | + SNDRV_PCM_RATE_48000 | + SNDRV_PCM_RATE_96000 | + SNDRV_PCM_RATE_192000, + .transfer_in = 0, + .must_be_clock_source = 0, + .tag = 2, + }, + /* + * XXX: later, support digital input, but the i2sbus needs + * to support this first! + */ + {} +}; + +static int topaz_usable(struct codec_info_item *cii, + struct transfer_info *ti, + struct transfer_info *out) +{ + return 1; +} + +static int topaz_prepare(struct codec_info_item *cii, + struct bus_info *bi, + struct snd_pcm_substream *substream) +{ + struct topaz *topaz = cii->codec_data; + int err = -EBUSY; + + mutex_lock(&topaz->mutex); + + switch (substream->runtime->rate) { + /* XXX: more rates ok? */ + case 32000: + case 44100: + case 48000: + case 96000: + case 192000: + /* + * these rates are ok + * + * FIXME: program spdif channel control bits here so that + * userspace doesn't have to if it only plays pcm! + */ + err = 0; + + /* set the clock control registers properly */ + + break; + default: + break; + } + + mutex_unlock(&topaz->mutex); + + return err; +} + +static int topaz_open(struct codec_info_item *cii, + struct snd_pcm_substream *substream) +{ + struct topaz *topaz = cii->codec_data; + + mutex_lock(&topaz->mutex); + topaz->open_count++; + + /* set the RUN bit */ + + mutex_unlock(&topaz->mutex); + + return 0; +} + +static int topaz_close(struct codec_info_item *cii, + struct snd_pcm_substream *substream) +{ + struct topaz *topaz = cii->codec_data; + + mutex_lock(&topaz->mutex); + topaz->open_count--; + + /* unset the RUN bit */ + + mutex_unlock(&topaz->mutex); + + return 0; +} + +static int topaz_switch_clock(struct codec_info_item *cii, + enum clock_switch what) +{ +// struct topaz *topaz = cii->codec_data; + + /* needed for digital input later */ + + return 0; +} + +#ifdef CONFIG_PM + +static int topaz_suspend(struct codec_info_item *cii, pm_message_t state) +{ + struct topaz *topaz = cii->codec_data; + int err = -ENXIO; + + mutex_lock(&topaz->mutex); + err = 0; + + /* nothing? + * back up registers? + * we should be stopped when suspending */ + + mutex_unlock(&topaz->mutex); + + return err; +} + +static int topaz_resume(struct codec_info_item *cii) +{ + struct topaz *topaz = cii->codec_data; + int err = -ENXIO; + + mutex_lock(&topaz->mutex); + + topaz_register_init(topaz); + err = 0; + mutex_unlock(&topaz->mutex); + + return err; +} + +#endif /* CONFIG_PM */ + +static struct codec_info topaz_codec_info = { + .transfers = topaz_transfers, + .sysclock_factor = 256, + .bus_factor = 64, + .owner = THIS_MODULE, + .usable = topaz_usable, + .prepare = topaz_prepare, + .open = topaz_open, + .close = topaz_close, + .switch_clock = topaz_switch_clock, +#ifdef CONFIG_PM + .suspend = topaz_suspend, + .resume = topaz_resume, +#endif +}; + +static int topaz_init_codec(struct aoa_codec *codec) +{ + struct topaz *topaz = codec_to_topaz(codec); + struct codec_info *ci = &topaz_codec_info; + int err; + u8 idver, ver; + + if (!topaz->codec.gpio || !topaz->codec.gpio->methods || + !topaz->codec.gpio->methods->set_dig_hw_reset) { + printk(KERN_ERR PFX "gpios not assigned!\n"); + return -ENODEV; + } + + /* reset codec */ + topaz->codec.gpio->methods->set_dig_hw_reset(topaz->codec.gpio, 0); + udelay(250); + topaz->codec.gpio->methods->set_dig_hw_reset(topaz->codec.gpio, 1); + udelay(250); + topaz->codec.gpio->methods->set_dig_hw_reset(topaz->codec.gpio, 0); + udelay(250); + + /* read codec version */ + if (topaz_read_register(topaz, TOPAZ_REG_VERSION, &idver) != 0) + return -ENODEV; + + ver = idver & TOPAZ_REG_VER_MASK; + + switch (idver & TOPAZ_REG_ID_MASK) { + case TOPAZ_ID_CS8406 << TOPAZ_REG_ID_SHIFT: + printk(KERN_DEBUG "Topaz CS8406 chip (ver %d)\n", ver); + break; + case TOPAZ_ID_CS8416 << TOPAZ_REG_ID_SHIFT: + printk(KERN_DEBUG "Topaz CS8416 chip (ver %d)\n", ver); + break; + case TOPAZ_ID_CS8420 << TOPAZ_REG_ID_SHIFT: + printk(KERN_DEBUG "Topaz CS8420 chip (ver %d)\n", ver); + break; + default: + printk(KERN_DEBUG "Unknown chip (id %d, ver %d)\n", + (idver & TOPAZ_REG_ID_MASK) << TOPAZ_REG_ID_SHIFT, ver); + return -ENODEV; + } + + if (topaz_register_init(topaz)) { + printk(KERN_ERR PFX "failed to initialise topaz registers\n"); + return -ENODEV; + } + + if (aoa_snd_device_new(SNDRV_DEV_LOWLEVEL, topaz, &ops)) { + printk(KERN_ERR PFX "failed to create topaz snd device!\n"); + return -ENODEV; + } + + /* nothing connected by fabric? error! */ + if ((topaz->codec.connected & 0xF) == 0) + return -ENOTCONN; + + if (topaz->codec.soundbus_dev->attach_codec(topaz->codec.soundbus_dev, + aoa_get_card(), + ci, topaz)) { + printk(KERN_ERR PFX "error creating topaz pcm\n"); + return -ENODEV; + } + + printk(KERN_INFO PFX "attached to topaz codec via i2c\n"); + + return 0; +// error: + topaz->codec.soundbus_dev->detach_codec(topaz->codec.soundbus_dev, topaz); + snd_device_free(aoa_get_card(), topaz); + return err; +} + +static void topaz_exit_codec(struct aoa_codec *codec) +{ + struct topaz *topaz = codec_to_topaz(codec); + + /* XXX: unset the RUN bit in the clock control register */ + + if (!topaz->codec.soundbus_dev) { + printk(KERN_ERR PFX "topaz_exit_codec called without soundbus_dev!\n"); + return; + } + topaz->codec.soundbus_dev->detach_codec(topaz->codec.soundbus_dev, topaz); +} + +static struct i2c_driver topaz_driver; + +static int topaz_create(struct i2c_adapter *adapter, + struct device_node *node, + int addr) +{ + struct topaz *topaz; + + topaz = kzalloc(sizeof(struct topaz), GFP_KERNEL); + + if (!topaz) + return -ENOMEM; + + mutex_init(&topaz->mutex); + topaz->i2c.driver = &topaz_driver; + topaz->i2c.adapter = adapter; + topaz->i2c.addr = addr & 0x7f; + strlcpy(topaz->i2c.name, "topaz audio codec", I2C_NAME_SIZE); + + if (i2c_attach_client(&topaz->i2c)) { + printk(KERN_ERR PFX "failed to attach to i2c\n"); + goto fail; + } + + strlcpy(topaz->codec.name, "topaz", MAX_CODEC_NAME_LEN); + topaz->codec.owner = THIS_MODULE; + topaz->codec.init = topaz_init_codec; + topaz->codec.exit = topaz_exit_codec; + topaz->codec.node = of_node_get(node); + + if (aoa_codec_register(&topaz->codec)) + goto detach; + + printk(KERN_DEBUG PFX "created and attached topaz instance\n"); + return 0; + detach: + i2c_detach_client(&topaz->i2c); + fail: + kfree(topaz); + return -EINVAL; +} + +static int topaz_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); + + if (!of_device_is_compatible(busnode, "k2-i2c")) + return -ENODEV; + + /* + * Just try talking to the i2c address of the chip, + * since there are no device-tree entries for it. + */ + + return topaz_create(adapter, NULL, 0x10); +} + +static int topaz_i2c_detach(struct i2c_client *client) +{ + struct topaz *topaz = container_of(client, struct topaz, i2c); + int err; + + if ((err = i2c_detach_client(client))) + return err; + aoa_codec_unregister(&topaz->codec); + of_node_put(topaz->codec.node); + if (topaz->codec_info) + kfree(topaz->codec_info); + kfree(topaz); + return 0; +} + +static struct i2c_driver topaz_driver = { + .driver = { + .name = "aoa_codec_topaz", + .owner = THIS_MODULE, + }, + .attach_adapter = topaz_i2c_attach, + .detach_client = topaz_i2c_detach, +}; + +static int __init topaz_init(void) +{ + return i2c_add_driver(&topaz_driver); +} + +static void __exit topaz_exit(void) +{ + i2c_del_driver(&topaz_driver); +} + +module_init(topaz_init); +module_exit(topaz_exit); --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ wireless-testing/sound/aoa/codecs/topaz.h 2009-11-11 20:20:07.000000000 +0100 @@ -0,0 +1,116 @@ +/* + * Apple Onboard Audio driver for Topaz codec (header) + * + * Copyright 2006, 2008 Johannes Berg + * + * GPL v2, can be found in COPYING. + */ +#ifndef __SND_AOA_CODEC_TOPAZ_H +#define __SND_AOA_CODEC_TOPAZ_H +#include +#include +#include +#include + +/* + * The bitnames are mostly taken from the chip docs, available at + * Cirrus Logic's website: + * http://www.cirrus.com/en/pubs/proDatasheet/CS8406_F4.pdf + * + * Reserved bits are not listed. + */ + +/* + * CS8406 register definitions, some of 8420 that were needed + * for digital output, 8416 is quite different. + */ + +#define TOPAZ_MAP_AUTOINCREMENT 0x80 + +#define TOPAZ_REG_CTL1 0x01 +#define TOPAZ_CTL1_SWCLK BIT(7) +#define TOPAZ_CTL1_VSET BIT(6) +#define TOPAZ_CTL1_MUTESAO BIT(5) +#define TOPAZ_CTL1_MUTEAES BIT(4) +#define TOPAZ_CTL1_DITH BIT(3) +#define TOPAZ_CTL1_INT_MASK (BIT(2) | BIT(1)) +#define TOPAZ_CTL1_INT_ACTHI (0 << 1) +#define TOPAZ_CTL1_INT_ACTLO (1 << 1) +#define TOPAZ_CTL1_INT_DRAIN (2 << 1) +#define TOPAZ_CTL1_TCBLD BIT(0) + +#define TOPAZ_REG_CTL2 0x02 +#define TOPAZ_CTL2_MMT BIT(2) +#define TOPAZ_CTL2_MMTCS BIT(1) +#define TOPAZ_CTL2_MMTLR BIT(0) + +#define TOPAZ_REG_DATAFLOWCONTROL 0x03 +#define TOPAZ_DFC_TXOFF BIT(6) +#define TOPAZ_DFC_AESBP BIT(5) + +#define TOPAZ_REG_CLOCKSOURCECONTROL 0x04 +#define TOPAZ_CSC_RUN BIT(6) +#define TOPAZ_CSC_CLK_MASK (BIT(5) | BIT(4)) +#define TOPAZ_CSC_CLK_256 (0 << 4) +#define TOPAZ_CSC_CLK_384 (1 << 4) +#define TOPAZ_CSC_CLK_512 (2 << 4) +#define TOPAZ_CSC_CLK_128 (3 << 4) + +#define TOPAZ_REG_DATAFORMAT 0x05 +#define TOPAZ_DF_SIMS BIT(7) +#define TOPAZ_DF_SISF BIT(6) +#define TOPAZ_DF_SIRES_MASK (BIT(5) | BIT(4)) +#define TOPAZ_DF_SIRES_24 (0 << 4) +#define TOPAZ_DF_SIRES_20 (1 << 4) +#define TOPAZ_DF_SIRES_16 (2 << 4) +#define TOPAZ_DF_SIJUST BIT(3) +#define TOPAZ_DF_SIDEL BIT(2) +#define TOPAZ_DF_SISPOL BIT(1) +#define TOPAZ_DF_SILRPOL BIT(0) + +#define TOPAZ_REG_INTSTAT1 0x07 +#define TOPAZ_REG_INTMASK1 0x09 +#define TOPAZ_REG_INTMODE1_0 0x0a +#define TOPAZ_REG_INTMODE1_1 0x0b +#define TOPAZ_INT1_TSLIP BIT(7) +#define TOPAZ_INT1_EFTC BIT(1) + +#define TOPAZ_REG_INTSTAT2 0x08 +#define TOPAZ_REG_INTMASK2 0x0c +#define TOPAZ_REG_INTMODE2_0 0x0d +#define TOPAZ_REG_INTMODE2_1 0x0e +#define TOPAZ_INT2_EFTU BIT(2) + +#define TOPAZ_REG_CSDBUFCTL 0x12 +#define TOPAZ_CSDBUFCTL_BSEL BIT(5) +#define TOPAZ_CSDBUFCTL_EFTCI BIT(2) +#define TOPAZ_CSDBUFCTL_CAM BIT(1) + +#define TOPAZ_REG_UDBUFCTL 0x13 +#define TOPAZ_UDBUFCTL_UD BIT(4) +#define TOPAZ_UDBUFCTL_UBM_MASK (BIT(3) | BIT(2)) +#define TOPAZ_UDBUFCTL_UBM_ZERO (0 << 2) +#define TOPAZ_UDBUFCTL_UBM_BLCK (1 << 2) +#define TOPAZ_UDBUFCTL_EFTUI BIT(0) + +/* 0x20 through 0x37 is the block-mode tx buffer */ +#define TOPAZ_REG_BLOCK_TX_BUFFER_START 0x20 + +#define TOPAZ_REG_VERSION 0x7f +#define TOPAZ_REG_ID_SHIFT 4 +#define TOPAZ_REG_ID_MASK (0xf << TOPAZ_REG_ID_SHIFT) +#define TOPAZ_REG_VER_MASK 0xf +#define TOPAZ_ID_CS8406 0xe +#define TOPAZ_VER_CS8406_A 1 +#define TOPAZ_VER_CS8406_B 2 +#define TOPAZ_ID_CS8416 2 +#define TOPAZ_VER_CS8416_A 1 +#define TOPAZ_VER_CS8416_B 2 +#define TOPAZ_VER_CS8416_C 3 +#define TOPAZ_VER_CS8416_D 7 +#define TOPAZ_ID_CS8420 1 +#define TOPAZ_VER_CS8420_A 1 +#define TOPAZ_VER_CS8420_B 3 +#define TOPAZ_VER_CS8420_C 4 + +#endif /* __SND_AOA_CODEC_TOPAZ_H */ --- wireless-testing.orig/sound/aoa/aoa-gpio.h 2009-11-03 10:21:06.000000000 +0100 +++ wireless-testing/sound/aoa/aoa-gpio.h 2009-11-11 20:20:07.000000000 +0100 @@ -42,6 +42,7 @@ struct gpio_methods { int (*get_master)(struct gpio_runtime *rt); void (*set_hw_reset)(struct gpio_runtime *rt, int on); + void (*set_dig_hw_reset)(struct gpio_runtime *rt, int on); /* use this to be notified of any events. The notification * function is passed the data, and is called in process --- wireless-testing.orig/sound/aoa/core/gpio-pmf.c 2009-11-03 10:21:06.000000000 +0100 +++ wireless-testing/sound/aoa/core/gpio-pmf.c 2009-11-11 20:20:07.000000000 +0100 @@ -46,6 +46,18 @@ static void pmf_gpio_set_hw_reset(struct " failed, rc: %d\n", rc); } +static void pmf_gpio_set_dig_hw_reset(struct gpio_runtime *rt, int on) +{ + struct pmf_args args = { .count = 1, .u[0].v = !!on }; + int rc; + + if (unlikely(!rt)) return; + rc = pmf_call_function(rt->node, "dig-hw-reset", &args); + if (rc) + printk(KERN_WARNING "pmf_gpio_set_dig_hw_reset" + " failed, rc: %d\n", rc); +} + static void pmf_gpio_all_amps_off(struct gpio_runtime *rt) { int saved; @@ -245,6 +257,7 @@ static struct gpio_methods methods = { .set_speakers = pmf_gpio_set_amp, .set_lineout = pmf_gpio_set_lineout, .set_hw_reset = pmf_gpio_set_hw_reset, + .set_dig_hw_reset = pmf_gpio_set_dig_hw_reset, .get_headphone = pmf_gpio_get_headphone, .get_speakers = pmf_gpio_get_amp, .get_lineout = pmf_gpio_get_lineout,