From b6482d48e536729829025262d6529df09ae20396 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Mon, 27 Jun 2005 15:32:43 +0200 Subject: [ALSA] hda-codec - Add 6stack model for ALC880 Documentation,HDA Codec driver - Added a new '6stack' model for ALC880. - Fixed the typo in 6stack-digout model name. - Added description for missing models in ALSA-Configuration.txt. Signed-off-by: Takashi Iwai --- Documentation/sound/alsa/ALSA-Configuration.txt | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'Documentation') diff --git a/Documentation/sound/alsa/ALSA-Configuration.txt b/Documentation/sound/alsa/ALSA-Configuration.txt index 104a994b8289..dfe44cf655d7 100644 --- a/Documentation/sound/alsa/ALSA-Configuration.txt +++ b/Documentation/sound/alsa/ALSA-Configuration.txt @@ -636,11 +636,16 @@ Prior to version 0.9.0rc4 options had a 'snd_' prefix. This was removed. 3stack-digout 3-jack in back, a HP out and a SPDIF out 5stack 5-jack in back, 2-jack in front 5stack-digout 5-jack in back, 2-jack in front, a SPDIF out + 6stack 6-jack in back, 2-jack in front + 6stack-digout 6-jack with a SPDIF out w810 3-jack z71v 3-jack (HP shared SPDIF) asus 3-jack uniwill 3-jack F1734 2-jack + test for testing/debugging purpose, almost all controls can be + adjusted. Appearing only when compiled with + $CONFIG_SND_DEBUG=y CMI9880 minimal 3-jack in back -- cgit v1.2.3-55-g7522 From 6d00a3127972e7853d6296ffc1e72c5b1a23d937 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Thu, 30 Jun 2005 13:40:51 +0200 Subject: [ALSA] Fix and clean-up of vxpocket driver Documentation,PCMCIA Kconfig,Digigram VX Pocket driver - Fixed Oops with request_firmware() - Detect the card type in runtime (vxpoocket v2 or 440) - snd-vxp440 driver is merged to snd-vxpocket - Clean up the code Signed-off-by: Takashi Iwai --- Documentation/sound/alsa/ALSA-Configuration.txt | 25 +- sound/pcmcia/Kconfig | 15 +- sound/pcmcia/vx/Makefile | 7 +- sound/pcmcia/vx/vxpocket.c | 427 +++++++++++++++++++++--- sound/pcmcia/vx/vxpocket.h | 27 +- 5 files changed, 379 insertions(+), 122 deletions(-) (limited to 'Documentation') diff --git a/Documentation/sound/alsa/ALSA-Configuration.txt b/Documentation/sound/alsa/ALSA-Configuration.txt index dfe44cf655d7..fe95bb2ae7fc 100644 --- a/Documentation/sound/alsa/ALSA-Configuration.txt +++ b/Documentation/sound/alsa/ALSA-Configuration.txt @@ -1376,7 +1376,7 @@ Prior to version 0.9.0rc4 options had a 'snd_' prefix. This was removed. Module snd-vxpocket ------------------- - Module for Digigram VX-Pocket VX2 PCMCIA card. + Module for Digigram VX-Pocket VX2 and 440 PCMCIA cards. ibl - Capture IBL size. (default = 0, minimum size) @@ -1396,29 +1396,6 @@ Prior to version 0.9.0rc4 options had a 'snd_' prefix. This was removed. Note: the driver is build only when CONFIG_ISA is set. - Module snd-vxp440 - ----------------- - - Module for Digigram VX-Pocket 440 PCMCIA card. - - ibl - Capture IBL size. (default = 0, minimum size) - - Module supports up to 8 cards. The module is compiled only when - PCMCIA is supported on kernel. - - To activate the driver via the card manager, you'll need to set - up /etc/pcmcia/vxp440.conf. See the sound/pcmcia/vx/vxp440.c. - - When the driver is compiled as a module and the hotplug firmware - is supported, the firmware data is loaded via hotplug automatically. - Install the necessary firmware files in alsa-firmware package. - When no hotplug fw loader is available, you need to load the - firmware via vxloader utility in alsa-tools package. - - About capture IBL, see the description of snd-vx222 module. - - Note: the driver is build only when CONFIG_ISA is set. - Module snd-ymfpci ----------------- diff --git a/sound/pcmcia/Kconfig b/sound/pcmcia/Kconfig index 3611e298834f..5d1b0b762efa 100644 --- a/sound/pcmcia/Kconfig +++ b/sound/pcmcia/Kconfig @@ -8,23 +8,12 @@ config SND_VXPOCKET depends on SND && PCMCIA && ISA select SND_VX_LIB help - Say Y here to include support for Digigram VXpocket - soundcards. + Say Y here to include support for Digigram VXpocket and + VXpocket 440 soundcards. To compile this driver as a module, choose M here: the module will be called snd-vxpocket. -config SND_VXP440 - tristate "Digigram VXpocket 440" - depends on SND && PCMCIA && ISA - select SND_VX_LIB - help - Say Y here to include support for Digigram VXpocket 440 - soundcards. - - To compile this driver as a module, choose M here: the module - will be called snd-vxp440. - config SND_PDAUDIOCF tristate "Sound Core PDAudioCF" depends on SND && PCMCIA && ISA diff --git a/sound/pcmcia/vx/Makefile b/sound/pcmcia/vx/Makefile index f35dfa1af094..54971f01e968 100644 --- a/sound/pcmcia/vx/Makefile +++ b/sound/pcmcia/vx/Makefile @@ -3,9 +3,6 @@ # Copyright (c) 2001 by Jaroslav Kysela # -snd-vx-cs-objs := vx_entry.o vxp_ops.o vxp_mixer.o -snd-vxpocket-objs := vxpocket.o -snd-vxp440-objs := vxp440.o +snd-vxpocket-objs := vxpocket.o vxp_ops.o vxp_mixer.o -obj-$(CONFIG_SND_VXPOCKET) += snd-vxpocket.o snd-vx-cs.o -obj-$(CONFIG_SND_VXP440) += snd-vxp440.o snd-vx-cs.o +obj-$(CONFIG_SND_VXPOCKET) += snd-vxpocket.o diff --git a/sound/pcmcia/vx/vxpocket.c b/sound/pcmcia/vx/vxpocket.c index 62d6fa128148..3a82161d3b24 100644 --- a/sound/pcmcia/vx/vxpocket.c +++ b/sound/pcmcia/vx/vxpocket.c @@ -24,21 +24,17 @@ #include #include #include "vxpocket.h" +#include +#include #include /* */ -#ifdef COMPILE_VXP440 -#define CARD_NAME "VXPocket440" -#else -#define CARD_NAME "VXPocket" -#endif - MODULE_AUTHOR("Takashi Iwai "); -MODULE_DESCRIPTION("Digigram " CARD_NAME); +MODULE_DESCRIPTION("Digigram VXPocket"); MODULE_LICENSE("GPL"); -MODULE_SUPPORTED_DEVICE("{{Digigram," CARD_NAME "}}"); +MODULE_SUPPORTED_DEVICE("{{Digigram,VXPocket},{Digigram,VXPocket440}}"); static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; /* Index 0-MAX */ static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; /* ID for this card */ @@ -46,82 +42,405 @@ static int enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP; /* Enable switches */ static int ibl[SNDRV_CARDS]; module_param_array(index, int, NULL, 0444); -MODULE_PARM_DESC(index, "Index value for " CARD_NAME " soundcard."); +MODULE_PARM_DESC(index, "Index value for VXPocket soundcard."); module_param_array(id, charp, NULL, 0444); -MODULE_PARM_DESC(id, "ID string for " CARD_NAME " soundcard."); +MODULE_PARM_DESC(id, "ID string for VXPocket soundcard."); module_param_array(enable, bool, NULL, 0444); -MODULE_PARM_DESC(enable, "Enable " CARD_NAME " soundcard."); +MODULE_PARM_DESC(enable, "Enable VXPocket soundcard."); module_param_array(ibl, int, NULL, 0444); -MODULE_PARM_DESC(ibl, "Capture IBL size for " CARD_NAME " soundcard."); +MODULE_PARM_DESC(ibl, "Capture IBL size for VXPocket soundcard."); /* */ -#ifdef COMPILE_VXP440 +static unsigned int card_alloc; +static dev_link_t *dev_list; /* Linked list of devices */ +static dev_info_t dev_info = "snd-vxpocket"; -/* 1 DSP, 1 sync UER, 1 sync World Clock (NIY) */ -/* SMPTE (NIY) */ -/* 2 stereo analog input (line/micro) */ -/* 2 stereo analog output */ -/* Only output levels can be modified */ -/* UER, but only for the first two inputs and outputs. */ -#define NUM_CODECS 2 -#define CARD_TYPE VX_TYPE_VXP440 -#define DEV_INFO "snd-vxp440" +static int vxpocket_event(event_t event, int priority, event_callback_args_t *args); -#else -/* 1 DSP, 1 sync UER */ -/* 1 programmable clock (NIY) */ -/* 1 stereo analog input (line/micro) */ -/* 1 stereo analog output */ -/* Only output levels can be modified */ +/* + */ +static void vxpocket_release(dev_link_t *link) +{ + if (link->state & DEV_CONFIG) { + /* release cs resources */ + pcmcia_release_configuration(link->handle); + pcmcia_release_io(link->handle, &link->io); + pcmcia_release_irq(link->handle, &link->irq); + link->state &= ~DEV_CONFIG; + } + if (link->handle) { + /* Break the link with Card Services */ + pcmcia_deregister_client(link->handle); + link->handle = NULL; + } +} -#define NUM_CODECS 1 -#define CARD_TYPE VX_TYPE_VXPOCKET -#define DEV_INFO "snd-vxpocket" +/* + * destructor, called from snd_card_free_in_thread() + */ +static int snd_vxpocket_dev_free(snd_device_t *device) +{ + vx_core_t *chip = device->device_data; -#endif + snd_vx_free_firmware(chip); + kfree(chip); + return 0; +} -static dev_info_t dev_info = DEV_INFO; -static struct snd_vx_hardware vxp_hw = { - .name = CARD_NAME, - .type = CARD_TYPE, +/* + * Hardware information + */ + +/* VX-pocket V2 + * + * 1 DSP, 1 sync UER + * 1 programmable clock (NIY) + * 1 stereo analog input (line/micro) + * 1 stereo analog output + * Only output levels can be modified + */ + +static struct snd_vx_hardware vxpocket_hw = { + .name = "VXPocket", + .type = VX_TYPE_VXPOCKET, /* hardware specs */ - .num_codecs = NUM_CODECS, - .num_ins = NUM_CODECS, - .num_outs = NUM_CODECS, + .num_codecs = 1, + .num_ins = 1, + .num_outs = 1, .output_level_max = VX_ANALOG_OUT_LEVEL_MAX, }; -static struct snd_vxp_entry hw_entry = { - .dev_info = &dev_info, +/* VX-pocket 440 + * + * 1 DSP, 1 sync UER, 1 sync World Clock (NIY) + * SMPTE (NIY) + * 2 stereo analog input (line/micro) + * 2 stereo analog output + * Only output levels can be modified + * UER, but only for the first two inputs and outputs. + */ - /* module parameters */ - .index_table = index, - .id_table = id, - .enable_table = enable, - .ibl = ibl, +static struct snd_vx_hardware vxp440_hw = { + .name = "VXPocket440", + .type = VX_TYPE_VXP440, + + /* hardware specs */ + .num_codecs = 2, + .num_ins = 2, + .num_outs = 2, + .output_level_max = VX_ANALOG_OUT_LEVEL_MAX, +}; + + +/* + * create vxpocket instance + */ +static struct snd_vxpocket *snd_vxpocket_new(snd_card_t *card, int ibl) +{ + client_reg_t client_reg; /* Register with cardmgr */ + dev_link_t *link; /* Info for cardmgr */ + vx_core_t *chip; + struct snd_vxpocket *vxp; + int ret; + static snd_device_ops_t ops = { + .dev_free = snd_vxpocket_dev_free, + }; + + chip = snd_vx_create(card, &vxpocket_hw, &snd_vxpocket_ops, + sizeof(struct snd_vxpocket) - sizeof(vx_core_t)); + if (! chip) + return NULL; + + if (snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops) < 0) { + kfree(chip); + return NULL; + } + chip->ibl.size = ibl; + + vxp = (struct snd_vxpocket *)chip; + + link = &vxp->link; + link->priv = chip; + + link->io.Attributes1 = IO_DATA_PATH_WIDTH_AUTO; + link->io.NumPorts1 = 16; + + link->irq.Attributes = IRQ_TYPE_EXCLUSIVE | IRQ_HANDLE_PRESENT; + + link->irq.IRQInfo1 = IRQ_LEVEL_ID; + link->irq.Handler = &snd_vx_irq_handler; + link->irq.Instance = chip; + + link->conf.Attributes = CONF_ENABLE_IRQ; + link->conf.Vcc = 50; + link->conf.IntType = INT_MEMORY_AND_IO; + link->conf.ConfigIndex = 1; + link->conf.Present = PRESENT_OPTION; + + /* Register with Card Services */ + memset(&client_reg, 0, sizeof(client_reg)); + client_reg.dev_info = &dev_info; + client_reg.EventMask = + CS_EVENT_CARD_INSERTION | CS_EVENT_CARD_REMOVAL +#ifdef CONFIG_PM + | CS_EVENT_RESET_PHYSICAL | CS_EVENT_CARD_RESET + | CS_EVENT_PM_SUSPEND | CS_EVENT_PM_RESUME +#endif + ; + client_reg.event_handler = &vxpocket_event; + client_reg.Version = 0x0210; + client_reg.event_callback_args.client_data = link; + + ret = pcmcia_register_client(&link->handle, &client_reg); + if (ret != CS_SUCCESS) { + cs_error(link->handle, RegisterClient, ret); + return NULL; + } + + return vxp; +} + + +/** + * snd_vxpocket_assign_resources - initialize the hardware and card instance. + * @port: i/o port for the card + * @irq: irq number for the card + * + * this function assigns the specified port and irq, boot the card, + * create pcm and control instances, and initialize the rest hardware. + * + * returns 0 if successful, or a negative error code. + */ +static int snd_vxpocket_assign_resources(vx_core_t *chip, int port, int irq) +{ + int err; + snd_card_t *card = chip->card; + struct snd_vxpocket *vxp = (struct snd_vxpocket *)chip; + + snd_printdd(KERN_DEBUG "vxpocket assign resources: port = 0x%x, irq = %d\n", port, irq); + vxp->port = port; + + sprintf(card->shortname, "Digigram %s", card->driver); + sprintf(card->longname, "%s at 0x%x, irq %i", + card->shortname, port, irq); + + chip->irq = irq; + + if ((err = snd_vx_setup_firmware(chip)) < 0) + return err; + + return 0; +} + + +/* + * configuration callback + */ + +#define CS_CHECK(fn, ret) \ +do { last_fn = (fn); if ((last_ret = (ret)) != 0) goto cs_failed; } while (0) + +static void vxpocket_config(dev_link_t *link) +{ + client_handle_t handle = link->handle; + vx_core_t *chip = link->priv; + struct snd_vxpocket *vxp = (struct snd_vxpocket *)chip; + tuple_t tuple; + cisparse_t *parse; + u_short buf[32]; + int last_fn, last_ret; + + snd_printdd(KERN_DEBUG "vxpocket_config called\n"); + parse = kmalloc(sizeof(*parse), GFP_KERNEL); + if (! parse) { + snd_printk(KERN_ERR "vx: cannot allocate\n"); + return; + } + tuple.Attributes = 0; + tuple.TupleData = (cisdata_t *)buf; + tuple.TupleDataMax = sizeof(buf); + tuple.TupleOffset = 0; + tuple.DesiredTuple = CISTPL_CONFIG; + CS_CHECK(GetFirstTuple, pcmcia_get_first_tuple(handle, &tuple)); + CS_CHECK(GetTupleData, pcmcia_get_tuple_data(handle, &tuple)); + CS_CHECK(ParseTuple, pcmcia_parse_tuple(handle, &tuple, parse)); + link->conf.ConfigBase = parse->config.base; + link->conf.Present = parse->config.rmask[0]; + + /* redefine hardware record according to the VERSION1 string */ + tuple.DesiredTuple = CISTPL_VERS_1; + CS_CHECK(GetFirstTuple, pcmcia_get_first_tuple(handle, &tuple)); + CS_CHECK(GetTupleData, pcmcia_get_tuple_data(handle, &tuple)); + CS_CHECK(ParseTuple, pcmcia_parse_tuple(handle, &tuple, parse)); + if (! strcmp(parse->version_1.str + parse->version_1.ofs[1], "VX-POCKET")) { + snd_printdd("VX-pocket is detected\n"); + } else { + snd_printdd("VX-pocket 440 is detected\n"); + /* overwrite the hardware information */ + chip->hw = &vxp440_hw; + chip->type = vxp440_hw.type; + strcpy(chip->card->driver, vxp440_hw.name); + } + + /* Configure card */ + link->state |= DEV_CONFIG; + + CS_CHECK(RequestIO, pcmcia_request_io(handle, &link->io)); + CS_CHECK(RequestIRQ, pcmcia_request_irq(link->handle, &link->irq)); + CS_CHECK(RequestConfiguration, pcmcia_request_configuration(link->handle, &link->conf)); + + chip->dev = &handle_to_dev(link->handle); + + if (snd_vxpocket_assign_resources(chip, link->io.BasePort1, link->irq.AssignedIRQ) < 0) + goto failed; + + link->dev = &vxp->node; + link->state &= ~DEV_CONFIG_PENDING; + kfree(parse); + return; + +cs_failed: + cs_error(link->handle, last_fn, last_ret); +failed: + pcmcia_release_configuration(link->handle); + pcmcia_release_io(link->handle, &link->io); + pcmcia_release_irq(link->handle, &link->irq); + link->state &= ~DEV_CONFIG; + kfree(parse); +} + + +/* + * event callback + */ +static int vxpocket_event(event_t event, int priority, event_callback_args_t *args) +{ + dev_link_t *link = args->client_data; + vx_core_t *chip = link->priv; + + switch (event) { + case CS_EVENT_CARD_REMOVAL: + snd_printdd(KERN_DEBUG "CARD_REMOVAL..\n"); + link->state &= ~DEV_PRESENT; + if (link->state & DEV_CONFIG) + chip->chip_status |= VX_STAT_IS_STALE; + break; + case CS_EVENT_CARD_INSERTION: + snd_printdd(KERN_DEBUG "CARD_INSERTION..\n"); + link->state |= DEV_PRESENT | DEV_CONFIG_PENDING; + vxpocket_config(link); + break; +#ifdef CONFIG_PM + case CS_EVENT_PM_SUSPEND: + snd_printdd(KERN_DEBUG "SUSPEND\n"); + link->state |= DEV_SUSPEND; + if (chip && chip->card->pm_suspend) { + snd_printdd(KERN_DEBUG "snd_vx_suspend calling\n"); + chip->card->pm_suspend(chip->card, PMSG_SUSPEND); + } + /* Fall through... */ + case CS_EVENT_RESET_PHYSICAL: + snd_printdd(KERN_DEBUG "RESET_PHYSICAL\n"); + if (link->state & DEV_CONFIG) + pcmcia_release_configuration(link->handle); + break; + case CS_EVENT_PM_RESUME: + snd_printdd(KERN_DEBUG "RESUME\n"); + link->state &= ~DEV_SUSPEND; + /* Fall through... */ + case CS_EVENT_CARD_RESET: + snd_printdd(KERN_DEBUG "CARD_RESET\n"); + if (DEV_OK(link)) { + //struct snd_vxpocket *vxp = (struct snd_vxpocket *)chip; + snd_printdd(KERN_DEBUG "requestconfig...\n"); + pcmcia_request_configuration(link->handle, &link->conf); + if (chip && chip->card->pm_resume) { + snd_printdd(KERN_DEBUG "calling snd_vx_resume\n"); + chip->card->pm_resume(chip->card); + } + } + snd_printdd(KERN_DEBUG "resume done!\n"); + break; +#endif + } + return 0; +} - /* h/w config */ - .hardware = &vxp_hw, - .ops = &snd_vxpocket_ops, -}; /* */ static dev_link_t *vxp_attach(void) { - return snd_vxpocket_attach(&hw_entry); + snd_card_t *card; + struct snd_vxpocket *vxp; + int i; + + /* find an empty slot from the card list */ + for (i = 0; i < SNDRV_CARDS; i++) { + if (! card_alloc & (1 << i)) + break; + } + if (i >= SNDRV_CARDS) { + snd_printk(KERN_ERR "vxpocket: too many cards found\n"); + return NULL; + } + if (! enable[i]) + return NULL; /* disabled explicitly */ + + /* ok, create a card instance */ + card = snd_card_new(index[i], id[i], THIS_MODULE, 0); + if (card == NULL) { + snd_printk(KERN_ERR "vxpocket: cannot create a card instance\n"); + return NULL; + } + + vxp = snd_vxpocket_new(card, ibl[i]); + if (! vxp) { + snd_card_free(card); + return NULL; + } + + vxp->index = index[i]; + card_alloc |= 1 << i; + + /* Chain drivers */ + vxp->link.next = dev_list; + dev_list = &vxp->link; + + return &vxp->link; } static void vxp_detach(dev_link_t *link) { - snd_vxpocket_detach(&hw_entry, link); + struct snd_vxpocket *vxp; + vx_core_t *chip; + dev_link_t **linkp; + + if (! link) + return; + + vxp = link->priv; + chip = (vx_core_t *)vxp; + card_alloc &= ~(1 << vxp->index); + + /* Remove the interface data from the linked list */ + for (linkp = &dev_list; *linkp; linkp = &(*linkp)->next) + if (*linkp == link) { + *linkp = link->next; + break; + } + + chip->chip_status |= VX_STAT_IS_STALE; /* to be sure */ + snd_card_disconnect(chip->card); + vxpocket_release(link); + snd_card_free_in_thread(chip->card); } /* @@ -137,7 +456,7 @@ MODULE_DEVICE_TABLE(pcmcia, vxp_ids); static struct pcmcia_driver vxp_cs_driver = { .owner = THIS_MODULE, .drv = { - .name = DEV_INFO, + .name = "snd-vxpocket", }, .attach = vxp_attach, .detach = vxp_detach, @@ -152,7 +471,7 @@ static int __init init_vxpocket(void) static void __exit exit_vxpocket(void) { pcmcia_unregister_driver(&vxp_cs_driver); - BUG_ON(hw_entry.dev_list != NULL); + BUG_ON(dev_list != NULL); } module_init(init_vxpocket); diff --git a/sound/pcmcia/vx/vxpocket.h b/sound/pcmcia/vx/vxpocket.h index 4462c04a4e8f..70754aa3dd11 100644 --- a/sound/pcmcia/vx/vxpocket.h +++ b/sound/pcmcia/vx/vxpocket.h @@ -28,24 +28,6 @@ #include #include -struct snd_vxp_entry { - dev_info_t *dev_info; - - /* module parameters */ - int *index_table; - char **id_table; - int *enable_table; - int *ibl; - - /* h/w config */ - struct snd_vx_hardware *hardware; - struct snd_vx_ops *ops; - - /* slots */ - vx_core_t *card_list[SNDRV_CARDS]; - dev_link_t *dev_list; /* Linked list of devices */ -}; - struct snd_vxpocket { vx_core_t core; @@ -57,8 +39,7 @@ struct snd_vxpocket { unsigned int regCDSP; /* current CDSP register */ unsigned int regDIALOG; /* current DIALOG register */ - int index; - struct snd_vxp_entry *hw_entry; + int index; /* card index */ /* pcmcia stuff */ dev_link_t link; @@ -70,12 +51,6 @@ extern struct snd_vx_ops snd_vxpocket_ops; void vx_set_mic_boost(vx_core_t *chip, int boost); void vx_set_mic_level(vx_core_t *chip, int level); -/* - * pcmcia stuff - */ -dev_link_t *snd_vxpocket_attach(struct snd_vxp_entry *hw); -void snd_vxpocket_detach(struct snd_vxp_entry *hw, dev_link_t *link); - int vxp_add_mic_controls(vx_core_t *chip); /* Constants used to access the CDSP register (0x08). */ -- cgit v1.2.3-55-g7522 From 1bd9debf25b8a5f5029d7619f43e4a9a775973d3 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Thu, 30 Jun 2005 18:26:20 +0200 Subject: [ALSA] Add DBRI driver on Sparcs Documentation,SPARC,/sparc/Makefile Add the DBRI driver on Sparcs by Martin Habets (moved from alsa-driver tree). Signed-off-by: Takashi Iwai --- Documentation/sound/alsa/ALSA-Configuration.txt | 7 + sound/sparc/Kconfig | 12 +- sound/sparc/Makefile | 4 +- sound/sparc/dbri.c | 2729 +++++++++++++++++++++++ 4 files changed, 2748 insertions(+), 4 deletions(-) create mode 100644 sound/sparc/dbri.c (limited to 'Documentation') diff --git a/Documentation/sound/alsa/ALSA-Configuration.txt b/Documentation/sound/alsa/ALSA-Configuration.txt index fe95bb2ae7fc..94887dbf1b4b 100644 --- a/Documentation/sound/alsa/ALSA-Configuration.txt +++ b/Documentation/sound/alsa/ALSA-Configuration.txt @@ -1178,6 +1178,13 @@ Prior to version 0.9.0rc4 options had a 'snd_' prefix. This was removed. Module supports up to 8 cards. + Module snd-sun-dbri (on sparc only) + ----------------------------------- + + Module for DBRI sound chips found on Sparcs. + + Module supports up to 8 cards. + Module snd-wavefront -------------------- diff --git a/sound/sparc/Kconfig b/sound/sparc/Kconfig index f0fce1daf67d..25a8a558ef92 100644 --- a/sound/sparc/Kconfig +++ b/sound/sparc/Kconfig @@ -13,7 +13,6 @@ config SND_SUN_AMD7930 To compile this driver as a module, choose M here: the module will be called snd-sun-amd7930. -# dep_tristate 'Sun DBRI' CONFIG_SND_SUN_DBRI $CONFIG_SND config SND_SUN_CS4231 tristate "Sun CS4231" depends on SND @@ -24,5 +23,14 @@ config SND_SUN_CS4231 To compile this driver as a module, choose M here: the module will be called snd-sun-cs4231. -endmenu +config SND_SUN_DBRI + tristate "Sun DBRI" + depends on SND && SBUS + select SND_PCM + help + Say Y here to include support for DBRI sound device on Sun. + To compile this driver as a module, choose M here: the module + will be called snd-sun-dbri. + +endmenu diff --git a/sound/sparc/Makefile b/sound/sparc/Makefile index 6809cc92d276..3cd89c67c2f2 100644 --- a/sound/sparc/Makefile +++ b/sound/sparc/Makefile @@ -4,9 +4,9 @@ # snd-sun-amd7930-objs := amd7930.o -#snd-sun-dbri-objs := dbri.o snd-sun-cs4231-objs := cs4231.o +snd-sun-dbri-objs := dbri.o obj-$(CONFIG_SND_SUN_AMD7930) += snd-sun-amd7930.o -#obj-$(CONFIG_SND_SUN_DBRI) += snd-sun-dbri.o obj-$(CONFIG_SND_SUN_CS4231) += snd-sun-cs4231.o +obj-$(CONFIG_SND_SUN_DBRI) += snd-sun-dbri.o diff --git a/sound/sparc/dbri.c b/sound/sparc/dbri.c new file mode 100644 index 000000000000..941c7b1e7ebb --- /dev/null +++ b/sound/sparc/dbri.c @@ -0,0 +1,2729 @@ +/* + * Driver for DBRI sound chip found on Sparcs. + * Copyright (C) 2004 Martin Habets (mhabets@users.sourceforge.net) + * + * Based entirely upon drivers/sbus/audio/dbri.c which is: + * Copyright (C) 1997 Rudolf Koenig (rfkoenig@immd4.informatik.uni-erlangen.de) + * Copyright (C) 1998, 1999 Brent Baccala (baccala@freesoft.org) + * + * This is the lowlevel driver for the DBRI & MMCODEC duo used for ISDN & AUDIO + * on Sun SPARCstation 10, 20, LX and Voyager models. + * + * - DBRI: AT&T T5900FX Dual Basic Rates ISDN Interface. It is a 32 channel + * data time multiplexer with ISDN support (aka T7259) + * Interfaces: SBus,ISDN NT & TE, CHI, 4 bits parallel. + * CHI: (spelled ki) Concentration Highway Interface (AT&T or Intel bus ?). + * Documentation: + * - "STP 4000SBus Dual Basic Rate ISDN (DBRI) Tranceiver" from + * Sparc Technology Business (courtesy of Sun Support) + * - Data sheet of the T7903, a newer but very similar ISA bus equivalent + * available from the Lucent (formarly AT&T microelectronics) home + * page. + * - http://www.freesoft.org/Linux/DBRI/ + * - MMCODEC: Crystal Semiconductor CS4215 16 bit Multimedia Audio Codec + * Interfaces: CHI, Audio In & Out, 2 bits parallel + * Documentation: from the Crystal Semiconductor home page. + * + * The DBRI is a 32 pipe machine, each pipe can transfer some bits between + * memory and a serial device (long pipes, nr 0-15) or between two serial + * devices (short pipes, nr 16-31), or simply send a fixed data to a serial + * device (short pipes). + * A timeslot defines the bit-offset and nr of bits read from a serial device. + * The timeslots are linked to 6 circular lists, one for each direction for + * each serial device (NT,TE,CHI). A timeslot is associated to 1 or 2 pipes + * (the second one is a monitor/tee pipe, valid only for serial input). + * + * The mmcodec is connected via the CHI bus and needs the data & some + * parameters (volume, balance, output selection) timemultiplexed in 8 byte + * chunks. It also has a control mode, which serves for audio format setting. + * + * Looking at the CS4215 data sheet it is easy to set up 2 or 4 codecs on + * the same CHI bus, so I thought perhaps it is possible to use the onboard + * & the speakerbox codec simultanously, giving 2 (not very independent :-) + * audio devices. But the SUN HW group decided against it, at least on my + * LX the speakerbox connector has at least 1 pin missing and 1 wrongly + * connected. + */ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +MODULE_AUTHOR("Rudolf Koenig, Brent Baccala and Martin Habets"); +MODULE_DESCRIPTION("Sun DBRI"); +MODULE_LICENSE("GPL"); +MODULE_SUPPORTED_DEVICE("{{Sun,DBRI}}"); + +static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; /* Index 0-MAX */ +static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; /* ID for this card */ +static int enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP; /* Enable this card */ + +module_param_array(index, int, NULL, 0444); +MODULE_PARM_DESC(index, "Index value for Sun DBRI soundcard."); +module_param_array(id, charp, NULL, 0444); +MODULE_PARM_DESC(id, "ID string for Sun DBRI soundcard."); +module_param_array(enable, bool, NULL, 0444); +MODULE_PARM_DESC(enable, "Enable Sun DBRI soundcard."); + +#define DBRI_DEBUG + +#define D_INT (1<<0) +#define D_GEN (1<<1) +#define D_CMD (1<<2) +#define D_MM (1<<3) +#define D_USR (1<<4) +#define D_DESC (1<<5) + +static int dbri_debug = 0; +module_param(dbri_debug, int, 0444); +MODULE_PARM_DESC(dbri_debug, "Debug value for Sun DBRI soundcard."); + +#ifdef DBRI_DEBUG +static char *cmds[] = { + "WAIT", "PAUSE", "JUMP", "IIQ", "REX", "SDP", "CDP", "DTS", + "SSP", "CHI", "NT", "TE", "CDEC", "TEST", "CDM", "RESRV" +}; + +#define dprintk(a, x...) if(dbri_debug & a) printk(KERN_DEBUG x) + +#define DBRI_CMD(cmd, intr, value) ((cmd << 28) | \ + (1 << 27) | \ + value) +#else +#define dprintk(a, x...) + +#define DBRI_CMD(cmd, intr, value) ((cmd << 28) | \ + (intr << 27) | \ + value) +#endif /* DBRI_DEBUG */ + +/*************************************************************************** + CS4215 specific definitions and structures +****************************************************************************/ + +struct cs4215 { + __u8 data[4]; /* Data mode: Time slots 5-8 */ + __u8 ctrl[4]; /* Ctrl mode: Time slots 1-4 */ + __u8 onboard; + __u8 offset; /* Bit offset from frame sync to time slot 1 */ + volatile __u32 status; + volatile __u32 version; + __u8 precision; /* In bits, either 8 or 16 */ + __u8 channels; /* 1 or 2 */ +}; + +/* + * Control mode first + */ + +/* Time Slot 1, Status register */ +#define CS4215_CLB (1<<2) /* Control Latch Bit */ +#define CS4215_OLB (1<<3) /* 1: line: 2.0V, speaker 4V */ + /* 0: line: 2.8V, speaker 8V */ +#define CS4215_MLB (1<<4) /* 1: Microphone: 20dB gain disabled */ +#define CS4215_RSRVD_1 (1<<5) + +/* Time Slot 2, Data Format Register */ +#define CS4215_DFR_LINEAR16 0 +#define CS4215_DFR_ULAW 1 +#define CS4215_DFR_ALAW 2 +#define CS4215_DFR_LINEAR8 3 +#define CS4215_DFR_STEREO (1<<2) +static struct { + unsigned short freq; + unsigned char xtal; + unsigned char csval; +} CS4215_FREQ[] = { + { 8000, (1 << 4), (0 << 3) }, + { 16000, (1 << 4), (1 << 3) }, + { 27429, (1 << 4), (2 << 3) }, /* Actually 24428.57 */ + { 32000, (1 << 4), (3 << 3) }, + /* { NA, (1 << 4), (4 << 3) }, */ + /* { NA, (1 << 4), (5 << 3) }, */ + { 48000, (1 << 4), (6 << 3) }, + { 9600, (1 << 4), (7 << 3) }, + { 5513, (2 << 4), (0 << 3) }, /* Actually 5512.5 */ + { 11025, (2 << 4), (1 << 3) }, + { 18900, (2 << 4), (2 << 3) }, + { 22050, (2 << 4), (3 << 3) }, + { 37800, (2 << 4), (4 << 3) }, + { 44100, (2 << 4), (5 << 3) }, + { 33075, (2 << 4), (6 << 3) }, + { 6615, (2 << 4), (7 << 3) }, + { 0, 0, 0} +}; + +#define CS4215_HPF (1<<7) /* High Pass Filter, 1: Enabled */ + +#define CS4215_12_MASK 0xfcbf /* Mask off reserved bits in slot 1 & 2 */ + +/* Time Slot 3, Serial Port Control register */ +#define CS4215_XEN (1<<0) /* 0: Enable serial output */ +#define CS4215_XCLK (1<<1) /* 1: Master mode: Generate SCLK */ +#define CS4215_BSEL_64 (0<<2) /* Bitrate: 64 bits per frame */ +#define CS4215_BSEL_128 (1<<2) +#define CS4215_BSEL_256 (2<<2) +#define CS4215_MCK_MAST (0<<4) /* Master clock */ +#define CS4215_MCK_XTL1 (1<<4) /* 24.576 MHz clock source */ +#define CS4215_MCK_XTL2 (2<<4) /* 16.9344 MHz clock source */ +#define CS4215_MCK_CLK1 (3<<4) /* Clockin, 256 x Fs */ +#define CS4215_MCK_CLK2 (4<<4) /* Clockin, see DFR */ + +/* Time Slot 4, Test Register */ +#define CS4215_DAD (1<<0) /* 0:Digital-Dig loop, 1:Dig-Analog-Dig loop */ +#define CS4215_ENL (1<<1) /* Enable Loopback Testing */ + +/* Time Slot 5, Parallel Port Register */ +/* Read only here and the same as the in data mode */ + +/* Time Slot 6, Reserved */ + +/* Time Slot 7, Version Register */ +#define CS4215_VERSION_MASK 0xf /* Known versions 0/C, 1/D, 2/E */ + +/* Time Slot 8, Reserved */ + +/* + * Data mode + */ +/* Time Slot 1-2: Left Channel Data, 2-3: Right Channel Data */ + +/* Time Slot 5, Output Setting */ +#define CS4215_LO(v) v /* Left Output Attenuation 0x3f: -94.5 dB */ +#define CS4215_LE (1<<6) /* Line Out Enable */ +#define CS4215_HE (1<<7) /* Headphone Enable */ + +/* Time Slot 6, Output Setting */ +#define CS4215_RO(v) v /* Right Output Attenuation 0x3f: -94.5 dB */ +#define CS4215_SE (1<<6) /* Speaker Enable */ +#define CS4215_ADI (1<<7) /* A/D Data Invalid: Busy in calibration */ + +/* Time Slot 7, Input Setting */ +#define CS4215_LG(v) v /* Left Gain Setting 0xf: 22.5 dB */ +#define CS4215_IS (1<<4) /* Input Select: 1=Microphone, 0=Line */ +#define CS4215_OVR (1<<5) /* 1: Overrange condition occurred */ +#define CS4215_PIO0 (1<<6) /* Parallel I/O 0 */ +#define CS4215_PIO1 (1<<7) + +/* Time Slot 8, Input Setting */ +#define CS4215_RG(v) v /* Right Gain Setting 0xf: 22.5 dB */ +#define CS4215_MA(v) (v<<4) /* Monitor Path Attenuation 0xf: mute */ + +/*************************************************************************** + DBRI specific definitions and structures +****************************************************************************/ + +/* DBRI main registers */ +#define REG0 0x00UL /* Status and Control */ +#define REG1 0x04UL /* Mode and Interrupt */ +#define REG2 0x08UL /* Parallel IO */ +#define REG3 0x0cUL /* Test */ +#define REG8 0x20UL /* Command Queue Pointer */ +#define REG9 0x24UL /* Interrupt Queue Pointer */ + +#define DBRI_NO_CMDS 64 +#define DBRI_NO_INTS 1 /* Note: the value of this define was + * originally 2. The ringbuffer to store + * interrupts in dma is currently broken. + * This is a temporary fix until the ringbuffer + * is fixed. + */ +#define DBRI_INT_BLK 64 +#define DBRI_NO_DESCS 64 +#define DBRI_NO_PIPES 32 + +#define DBRI_MM_ONB 1 +#define DBRI_MM_SB 2 + +#define DBRI_REC 0 +#define DBRI_PLAY 1 +#define DBRI_NO_STREAMS 2 + +/* One transmit/receive descriptor */ +struct dbri_mem { + volatile __u32 word1; + volatile __u32 ba; /* Transmit/Receive Buffer Address */ + volatile __u32 nda; /* Next Descriptor Address */ + volatile __u32 word4; +}; + +/* This structure is in a DMA region where it can accessed by both + * the CPU and the DBRI + */ +struct dbri_dma { + volatile s32 cmd[DBRI_NO_CMDS]; /* Place for commands */ + volatile s32 intr[DBRI_NO_INTS * DBRI_INT_BLK]; /* Interrupt field */ + struct dbri_mem desc[DBRI_NO_DESCS]; /* Xmit/receive descriptors */ +}; + +#define dbri_dma_off(member, elem) \ + ((u32)(unsigned long) \ + (&(((struct dbri_dma *)0)->member[elem]))) + +enum in_or_out { PIPEinput, PIPEoutput }; + +struct dbri_pipe { + u32 sdp; /* SDP command word */ + enum in_or_out direction; + int nextpipe; /* Next pipe in linked list */ + int prevpipe; + int cycle; /* Offset of timeslot (bits) */ + int length; /* Length of timeslot (bits) */ + int first_desc; /* Index of first descriptor */ + int desc; /* Index of active descriptor */ + volatile __u32 *recv_fixed_ptr; /* Ptr to receive fixed data */ +}; + +struct dbri_desc { + int inuse; /* Boolean flag */ + int next; /* Index of next desc, or -1 */ + unsigned int len; +}; + +/* Per stream (playback or record) information */ +typedef struct dbri_streaminfo { + snd_pcm_substream_t *substream; + u32 dvma_buffer; /* Device view of Alsa DMA buffer */ + int left; /* # of bytes left in DMA buffer */ + int size; /* Size of DMA buffer */ + size_t offset; /* offset in user buffer */ + int pipe; /* Data pipe used */ + int left_gain; /* mixer elements */ + int right_gain; + int balance; +} dbri_streaminfo_t; + +/* This structure holds the information for both chips (DBRI & CS4215) */ +typedef struct snd_dbri { + snd_card_t *card; /* ALSA card */ + snd_pcm_t *pcm; + + int regs_size, irq; /* Needed for unload */ + struct sbus_dev *sdev; /* SBUS device info */ + spinlock_t lock; + + volatile struct dbri_dma *dma; /* Pointer to our DMA block */ + u32 dma_dvma; /* DBRI visible DMA address */ + + void __iomem *regs; /* dbri HW regs */ + int dbri_version; /* 'e' and up is OK */ + int dbri_irqp; /* intr queue pointer */ + int wait_seen; + + struct dbri_pipe pipes[DBRI_NO_PIPES]; /* DBRI's 32 data pipes */ + struct dbri_desc descs[DBRI_NO_DESCS]; + + int chi_in_pipe; + int chi_out_pipe; + int chi_bpf; + + struct cs4215 mm; /* mmcodec special info */ + /* per stream (playback/record) info */ + struct dbri_streaminfo stream_info[DBRI_NO_STREAMS]; + + struct snd_dbri *next; +} snd_dbri_t; + +/* Needed for the ALSA macros to work */ +#define chip_t snd_dbri_t + +#define DBRI_MAX_VOLUME 63 /* Output volume */ +#define DBRI_MAX_GAIN 15 /* Input gain */ +#define DBRI_RIGHT_BALANCE 255 +#define DBRI_MID_BALANCE (DBRI_RIGHT_BALANCE >> 1) + +/* DBRI Reg0 - Status Control Register - defines. (Page 17) */ +#define D_P (1<<15) /* Program command & queue pointer valid */ +#define D_G (1<<14) /* Allow 4-Word SBus Burst */ +#define D_S (1<<13) /* Allow 16-Word SBus Burst */ +#define D_E (1<<12) /* Allow 8-Word SBus Burst */ +#define D_X (1<<7) /* Sanity Timer Disable */ +#define D_T (1<<6) /* Permit activation of the TE interface */ +#define D_N (1<<5) /* Permit activation of the NT interface */ +#define D_C (1<<4) /* Permit activation of the CHI interface */ +#define D_F (1<<3) /* Force Sanity Timer Time-Out */ +#define D_D (1<<2) /* Disable Master Mode */ +#define D_H (1<<1) /* Halt for Analysis */ +#define D_R (1<<0) /* Soft Reset */ + +/* DBRI Reg1 - Mode and Interrupt Register - defines. (Page 18) */ +#define D_LITTLE_END (1<<8) /* Byte Order */ +#define D_BIG_END (0<<8) /* Byte Order */ +#define D_MRR (1<<4) /* Multiple Error Ack on SBus (readonly) */ +#define D_MLE (1<<3) /* Multiple Late Error on SBus (readonly) */ +#define D_LBG (1<<2) /* Lost Bus Grant on SBus (readonly) */ +#define D_MBE (1<<1) /* Burst Error on SBus (readonly) */ +#define D_IR (1<<0) /* Interrupt Indicator (readonly) */ + +/* DBRI Reg2 - Parallel IO Register - defines. (Page 18) */ +#define D_ENPIO3 (1<<7) /* Enable Pin 3 */ +#define D_ENPIO2 (1<<6) /* Enable Pin 2 */ +#define D_ENPIO1 (1<<5) /* Enable Pin 1 */ +#define D_ENPIO0 (1<<4) /* Enable Pin 0 */ +#define D_ENPIO (0xf0) /* Enable all the pins */ +#define D_PIO3 (1<<3) /* Pin 3: 1: Data mode, 0: Ctrl mode */ +#define D_PIO2 (1<<2) /* Pin 2: 1: Onboard PDN */ +#define D_PIO1 (1<<1) /* Pin 1: 0: Reset */ +#define D_PIO0 (1<<0) /* Pin 0: 1: Speakerbox PDN */ + +/* DBRI Commands (Page 20) */ +#define D_WAIT 0x0 /* Stop execution */ +#define D_PAUSE 0x1 /* Flush long pipes */ +#define D_JUMP 0x2 /* New command queue */ +#define D_IIQ 0x3 /* Initialize Interrupt Queue */ +#define D_REX 0x4 /* Report command execution via interrupt */ +#define D_SDP 0x5 /* Setup Data Pipe */ +#define D_CDP 0x6 /* Continue Data Pipe (reread NULL Pointer) */ +#define D_DTS 0x7 /* Define Time Slot */ +#define D_SSP 0x8 /* Set short Data Pipe */ +#define D_CHI 0x9 /* Set CHI Global Mode */ +#define D_NT 0xa /* NT Command */ +#define D_TE 0xb /* TE Command */ +#define D_CDEC 0xc /* Codec setup */ +#define D_TEST 0xd /* No comment */ +#define D_CDM 0xe /* CHI Data mode command */ + +/* Special bits for some commands */ +#define D_PIPE(v) ((v)<<0) /* Pipe Nr: 0-15 long, 16-21 short */ + +/* Setup Data Pipe */ +/* IRM */ +#define D_SDP_2SAME (1<<18) /* Report 2nd time in a row value rcvd */ +#define D_SDP_CHANGE (2<<18) /* Report any changes */ +#define D_SDP_EVERY (3<<18) /* Report any changes */ +#define D_SDP_EOL (1<<17) /* EOL interrupt enable */ +#define D_SDP_IDLE (1<<16) /* HDLC idle interrupt enable */ + +/* Pipe data MODE */ +#define D_SDP_MEM (0<<13) /* To/from memory */ +#define D_SDP_HDLC (2<<13) +#define D_SDP_HDLC_D (3<<13) /* D Channel (prio control) */ +#define D_SDP_SER (4<<13) /* Serial to serial */ +#define D_SDP_FIXED (6<<13) /* Short only */ +#define D_SDP_MODE(v) ((v)&(7<<13)) + +#define D_SDP_TO_SER (1<<12) /* Direction */ +#define D_SDP_FROM_SER (0<<12) /* Direction */ +#define D_SDP_MSB (1<<11) /* Bit order within Byte */ +#define D_SDP_LSB (0<<11) /* Bit order within Byte */ +#define D_SDP_P (1<<10) /* Pointer Valid */ +#define D_SDP_A (1<<8) /* Abort */ +#define D_SDP_C (1<<7) /* Clear */ + +/* Define Time Slot */ +#define D_DTS_VI (1<<17) /* Valid Input Time-Slot Descriptor */ +#define D_DTS_VO (1<<16) /* Valid Output Time-Slot Descriptor */ +#define D_DTS_INS (1<<15) /* Insert Time Slot */ +#define D_DTS_DEL (0<<15) /* Delete Time Slot */ +#define D_DTS_PRVIN(v) ((v)<<10) /* Previous In Pipe */ +#define D_DTS_PRVOUT(v) ((v)<<5) /* Previous Out Pipe */ + +/* Time Slot defines */ +#define D_TS_LEN(v) ((v)<<24) /* Number of bits in this time slot */ +#define D_TS_CYCLE(v) ((v)<<14) /* Bit Count at start of TS */ +#define D_TS_DI (1<<13) /* Data Invert */ +#define D_TS_1CHANNEL (0<<10) /* Single Channel / Normal mode */ +#define D_TS_MONITOR (2<<10) /* Monitor pipe */ +#define D_TS_NONCONTIG (3<<10) /* Non contiguous mode */ +#define D_TS_ANCHOR (7<<10) /* Starting short pipes */ +#define D_TS_MON(v) ((v)<<5) /* Monitor Pipe */ +#define D_TS_NEXT(v) ((v)<<0) /* Pipe Nr: 0-15 long, 16-21 short */ + +/* Concentration Highway Interface Modes */ +#define D_CHI_CHICM(v) ((v)<<16) /* Clock mode */ +#define D_CHI_IR (1<<15) /* Immediate Interrupt Report */ +#define D_CHI_EN (1<<14) /* CHIL Interrupt enabled */ +#define D_CHI_OD (1<<13) /* Open Drain Enable */ +#define D_CHI_FE (1<<12) /* Sample CHIFS on Rising Frame Edge */ +#define D_CHI_FD (1<<11) /* Frame Drive */ +#define D_CHI_BPF(v) ((v)<<0) /* Bits per Frame */ + +/* NT: These are here for completeness */ +#define D_NT_FBIT (1<<17) /* Frame Bit */ +#define D_NT_NBF (1<<16) /* Number of bad frames to loose framing */ +#define D_NT_IRM_IMM (1<<15) /* Interrupt Report & Mask: Immediate */ +#define D_NT_IRM_EN (1<<14) /* Interrupt Report & Mask: Enable */ +#define D_NT_ISNT (1<<13) /* Configfure interface as NT */ +#define D_NT_FT (1<<12) /* Fixed Timing */ +#define D_NT_EZ (1<<11) /* Echo Channel is Zeros */ +#define D_NT_IFA (1<<10) /* Inhibit Final Activation */ +#define D_NT_ACT (1<<9) /* Activate Interface */ +#define D_NT_MFE (1<<8) /* Multiframe Enable */ +#define D_NT_RLB(v) ((v)<<5) /* Remote Loopback */ +#define D_NT_LLB(v) ((v)<<2) /* Local Loopback */ +#define D_NT_FACT (1<<1) /* Force Activation */ +#define D_NT_ABV (1<<0) /* Activate Bipolar Violation */ + +/* Codec Setup */ +#define D_CDEC_CK(v) ((v)<<24) /* Clock Select */ +#define D_CDEC_FED(v) ((v)<<12) /* FSCOD Falling Edge Delay */ +#define D_CDEC_RED(v) ((v)<<0) /* FSCOD Rising Edge Delay */ + +/* Test */ +#define D_TEST_RAM(v) ((v)<<16) /* RAM Pointer */ +#define D_TEST_SIZE(v) ((v)<<11) /* */ +#define D_TEST_ROMONOFF 0x5 /* Toggle ROM opcode monitor on/off */ +#define D_TEST_PROC 0x6 /* MicroProcessor test */ +#define D_TEST_SER 0x7 /* Serial-Controller test */ +#define D_TEST_RAMREAD 0x8 /* Copy from Ram to system memory */ +#define D_TEST_RAMWRITE 0x9 /* Copy into Ram from system memory */ +#define D_TEST_RAMBIST 0xa /* RAM Built-In Self Test */ +#define D_TEST_MCBIST 0xb /* Microcontroller Built-In Self Test */ +#define D_TEST_DUMP 0xe /* ROM Dump */ + +/* CHI Data Mode */ +#define D_CDM_THI (1<<8) /* Transmit Data on CHIDR Pin */ +#define D_CDM_RHI (1<<7) /* Receive Data on CHIDX Pin */ +#define D_CDM_RCE (1<<6) /* Receive on Rising Edge of CHICK */ +#define D_CDM_XCE (1<<2) /* Transmit Data on Rising Edge of CHICK */ +#define D_CDM_XEN (1<<1) /* Transmit Highway Enable */ +#define D_CDM_REN (1<<0) /* Receive Highway Enable */ + +/* The Interrupts */ +#define D_INTR_BRDY 1 /* Buffer Ready for processing */ +#define D_INTR_MINT 2 /* Marked Interrupt in RD/TD */ +#define D_INTR_IBEG 3 /* Flag to idle transition detected (HDLC) */ +#define D_INTR_IEND 4 /* Idle to flag transition detected (HDLC) */ +#define D_INTR_EOL 5 /* End of List */ +#define D_INTR_CMDI 6 /* Command has bean read */ +#define D_INTR_XCMP 8 /* Transmission of frame complete */ +#define D_INTR_SBRI 9 /* BRI status change info */ +#define D_INTR_FXDT 10 /* Fixed data change */ +#define D_INTR_CHIL 11 /* CHI lost frame sync (channel 36 only) */ +#define D_INTR_COLL 11 /* Unrecoverable D-Channel collision */ +#define D_INTR_DBYT 12 /* Dropped by frame slip */ +#define D_INTR_RBYT 13 /* Repeated by frame slip */ +#define D_INTR_LINT 14 /* Lost Interrupt */ +#define D_INTR_UNDR 15 /* DMA underrun */ + +#define D_INTR_TE 32 +#define D_INTR_NT 34 +#define D_INTR_CHI 36 +#define D_INTR_CMD 38 + +#define D_INTR_GETCHAN(v) (((v)>>24) & 0x3f) +#define D_INTR_GETCODE(v) (((v)>>20) & 0xf) +#define D_INTR_GETCMD(v) (((v)>>16) & 0xf) +#define D_INTR_GETVAL(v) ((v) & 0xffff) +#define D_INTR_GETRVAL(v) ((v) & 0xfffff) + +#define D_P_0 0 /* TE receive anchor */ +#define D_P_1 1 /* TE transmit anchor */ +#define D_P_2 2 /* NT transmit anchor */ +#define D_P_3 3 /* NT receive anchor */ +#define D_P_4 4 /* CHI send data */ +#define D_P_5 5 /* CHI receive data */ +#define D_P_6 6 /* */ +#define D_P_7 7 /* */ +#define D_P_8 8 /* */ +#define D_P_9 9 /* */ +#define D_P_10 10 /* */ +#define D_P_11 11 /* */ +#define D_P_12 12 /* */ +#define D_P_13 13 /* */ +#define D_P_14 14 /* */ +#define D_P_15 15 /* */ +#define D_P_16 16 /* CHI anchor pipe */ +#define D_P_17 17 /* CHI send */ +#define D_P_18 18 /* CHI receive */ +#define D_P_19 19 /* CHI receive */ +#define D_P_20 20 /* CHI receive */ +#define D_P_21 21 /* */ +#define D_P_22 22 /* */ +#define D_P_23 23 /* */ +#define D_P_24 24 /* */ +#define D_P_25 25 /* */ +#define D_P_26 26 /* */ +#define D_P_27 27 /* */ +#define D_P_28 28 /* */ +#define D_P_29 29 /* */ +#define D_P_30 30 /* */ +#define D_P_31 31 /* */ + +/* Transmit descriptor defines */ +#define DBRI_TD_F (1<<31) /* End of Frame */ +#define DBRI_TD_D (1<<30) /* Do not append CRC */ +#define DBRI_TD_CNT(v) ((v)<<16) /* Number of valid bytes in the buffer */ +#define DBRI_TD_B (1<<15) /* Final interrupt */ +#define DBRI_TD_M (1<<14) /* Marker interrupt */ +#define DBRI_TD_I (1<<13) /* Transmit Idle Characters */ +#define DBRI_TD_FCNT(v) (v) /* Flag Count */ +#define DBRI_TD_UNR (1<<3) /* Underrun: transmitter is out of data */ +#define DBRI_TD_ABT (1<<2) /* Abort: frame aborted */ +#define DBRI_TD_TBC (1<<0) /* Transmit buffer Complete */ +#define DBRI_TD_STATUS(v) ((v)&0xff) /* Transmit status */ + /* Maximum buffer size per TD: almost 8Kb */ +#define DBRI_TD_MAXCNT ((1 << 13) - 1) + +/* Receive descriptor defines */ +#define DBRI_RD_F (1<<31) /* End of Frame */ +#define DBRI_RD_C (1<<30) /* Completed buffer */ +#define DBRI_RD_B (1<<15) /* Final interrupt */ +#define DBRI_RD_M (1<<14) /* Marker interrupt */ +#define DBRI_RD_BCNT(v) (v) /* Buffer size */ +#define DBRI_RD_CRC (1<<7) /* 0: CRC is correct */ +#define DBRI_RD_BBC (1<<6) /* 1: Bad Byte received */ +#define DBRI_RD_ABT (1<<5) /* Abort: frame aborted */ +#define DBRI_RD_OVRN (1<<3) /* Overrun: data lost */ +#define DBRI_RD_STATUS(v) ((v)&0xff) /* Receive status */ +#define DBRI_RD_CNT(v) (((v)>>16)&0x1fff) /* Valid bytes in the buffer */ + +/* stream_info[] access */ +/* Translate the ALSA direction into the array index */ +#define DBRI_STREAMNO(substream) \ + (substream->stream == \ + SNDRV_PCM_STREAM_PLAYBACK? DBRI_PLAY: DBRI_REC) + +/* Return a pointer to dbri_streaminfo */ +#define DBRI_STREAM(dbri, substream) &dbri->stream_info[DBRI_STREAMNO(substream)] + +static snd_dbri_t *dbri_list = NULL; /* All DBRI devices */ + +/* + * Short data pipes transmit LSB first. The CS4215 receives MSB first. Grrr. + * So we have to reverse the bits. Note: not all bit lengths are supported + */ +static __u32 reverse_bytes(__u32 b, int len) +{ + switch (len) { + case 32: + b = ((b & 0xffff0000) >> 16) | ((b & 0x0000ffff) << 16); + case 16: + b = ((b & 0xff00ff00) >> 8) | ((b & 0x00ff00ff) << 8); + case 8: + b = ((b & 0xf0f0f0f0) >> 4) | ((b & 0x0f0f0f0f) << 4); + case 4: + b = ((b & 0xcccccccc) >> 2) | ((b & 0x33333333) << 2); + case 2: + b = ((b & 0xaaaaaaaa) >> 1) | ((b & 0x55555555) << 1); + case 1: + case 0: + break; + default: + printk(KERN_ERR "DBRI reverse_bytes: unsupported length\n"); + }; + + return b; +} + +/* +**************************************************************************** +************** DBRI initialization and command synchronization ************* +**************************************************************************** + +Commands are sent to the DBRI by building a list of them in memory, +then writing the address of the first list item to DBRI register 8. +The list is terminated with a WAIT command, which can generate a +CPU interrupt if required. + +Since the DBRI can run in parallel with the CPU, several means of +synchronization present themselves. The original scheme (Rudolf's) +was to set a flag when we "cmdlock"ed the DBRI, clear the flag when +an interrupt signaled completion, and wait on a wait_queue if a routine +attempted to cmdlock while the flag was set. The problems arose when +we tried to cmdlock from inside an interrupt handler, which might +cause scheduling in an interrupt (if we waited), etc, etc + +A more sophisticated scheme might involve a circular command buffer +or an array of command buffers. A routine could fill one with +commands and link it onto a list. When a interrupt signaled +completion of the current command buffer, look on the list for +the next one. + +I've decided to implement something much simpler - after each command, +the CPU waits for the DBRI to finish the command by polling the P bit +in DBRI register 0. I've tried to implement this in such a way +that might make implementing a more sophisticated scheme easier. + +Every time a routine wants to write commands to the DBRI, it must +first call dbri_cmdlock() and get an initial pointer into dbri->dma->cmd +in return. After the commands have been writen, dbri_cmdsend() is +called with the final pointer value. + +*/ + +enum dbri_lock_t { NoGetLock, GetLock }; + +static volatile s32 *dbri_cmdlock(snd_dbri_t * dbri, enum dbri_lock_t get) +{ +#ifndef SMP + if ((get == GetLock) && spin_is_locked(&dbri->lock)) { + printk(KERN_ERR "DBRI: cmdlock called while in spinlock."); + } +#endif + + /*if (get == GetLock) spin_lock(&dbri->lock); */ + return &dbri->dma->cmd[0]; +} + +static void dbri_process_interrupt_buffer(snd_dbri_t *); + +static void dbri_cmdsend(snd_dbri_t * dbri, volatile s32 * cmd) +{ + int MAXLOOPS = 1000000; + int maxloops = MAXLOOPS; + volatile s32 *ptr; + + for (ptr = &dbri->dma->cmd[0]; ptr < cmd; ptr++) { + dprintk(D_CMD, "cmd: %lx:%08x\n", (unsigned long)ptr, *ptr); + } + + if ((cmd - &dbri->dma->cmd[0]) >= DBRI_NO_CMDS - 1) { + printk("DBRI: Command buffer overflow! (bug in driver)\n"); + /* Ignore the last part. */ + cmd = &dbri->dma->cmd[DBRI_NO_CMDS - 3]; + } + + *(cmd++) = DBRI_CMD(D_PAUSE, 0, 0); + *(cmd++) = DBRI_CMD(D_WAIT, 1, 0); + dbri->wait_seen = 0; + sbus_writel(dbri->dma_dvma, dbri->regs + REG8); + while ((--maxloops) > 0 && (sbus_readl(dbri->regs + REG0) & D_P)) + barrier(); + if (maxloops == 0) { + printk(KERN_ERR "DBRI: Chip never completed command buffer\n"); + dprintk(D_CMD, "DBRI: Chip never completed command buffer\n"); + } else { + while ((--maxloops) > 0 && (!dbri->wait_seen)) + dbri_process_interrupt_buffer(dbri); + if (maxloops == 0) { + printk(KERN_ERR "DBRI: Chip never acked WAIT\n"); + dprintk(D_CMD, "DBRI: Chip never acked WAIT\n"); + } else { + dprintk(D_CMD, "Chip completed command " + "buffer (%d)\n", MAXLOOPS - maxloops); + } + } + + /*spin_unlock(&dbri->lock); */ +} + +/* Lock must be held when calling this */ +static void dbri_reset(snd_dbri_t * dbri) +{ + int i; + + dprintk(D_GEN, "reset 0:%x 2:%x 8:%x 9:%x\n", + sbus_readl(dbri->regs + REG0), + sbus_readl(dbri->regs + REG2), + sbus_readl(dbri->regs + REG8), sbus_readl(dbri->regs + REG9)); + + sbus_writel(D_R, dbri->regs + REG0); /* Soft Reset */ + for (i = 0; (sbus_readl(dbri->regs + REG0) & D_R) && i < 64; i++) + udelay(10); +} + +/* Lock must not be held before calling this */ +static void dbri_initialize(snd_dbri_t * dbri) +{ + volatile s32 *cmd; + u32 dma_addr, tmp; + unsigned long flags; + int n; + + spin_lock_irqsave(&dbri->lock, flags); + + dbri_reset(dbri); + + cmd = dbri_cmdlock(dbri, NoGetLock); + dprintk(D_GEN, "init: cmd: %p, int: %p\n", + &dbri->dma->cmd[0], &dbri->dma->intr[0]); + + /* + * Initialize the interrupt ringbuffer. + */ + for (n = 0; n < DBRI_NO_INTS - 1; n++) { + dma_addr = dbri->dma_dvma; + dma_addr += dbri_dma_off(intr, ((n + 1) & DBRI_INT_BLK)); + dbri->dma->intr[n * DBRI_INT_BLK] = dma_addr; + } + dma_addr = dbri->dma_dvma + dbri_dma_off(intr, 0); + dbri->dma->intr[n * DBRI_INT_BLK] = dma_addr; + dbri->dbri_irqp = 1; + + /* Initialize pipes */ + for (n = 0; n < DBRI_NO_PIPES; n++) + dbri->pipes[n].desc = dbri->pipes[n].first_desc = -1; + + /* We should query the openprom to see what burst sizes this + * SBus supports. For now, just disable all SBus bursts */ + tmp = sbus_readl(dbri->regs + REG0); + tmp &= ~(D_G | D_S | D_E); + sbus_writel(tmp, dbri->regs + REG0); + + /* + * Set up the interrupt queue + */ + dma_addr = dbri->dma_dvma + dbri_dma_off(intr, 0); + *(cmd++) = DBRI_CMD(D_IIQ, 0, 0); + *(cmd++) = dma_addr; + + dbri_cmdsend(dbri, cmd); + spin_unlock_irqrestore(&dbri->lock, flags); +} + +/* +**************************************************************************** +************************** DBRI data pipe management *********************** +**************************************************************************** + +While DBRI control functions use the command and interrupt buffers, the +main data path takes the form of data pipes, which can be short (command +and interrupt driven), or long (attached to DMA buffers). These functions +provide a rudimentary means of setting up and managing the DBRI's pipes, +but the calling functions have to make sure they respect the pipes' linked +list ordering, among other things. The transmit and receive functions +here interface closely with the transmit and receive interrupt code. + +*/ +static int pipe_active(snd_dbri_t * dbri, int pipe) +{ + return ((pipe >= 0) && (dbri->pipes[pipe].desc != -1)); +} + +/* reset_pipe(dbri, pipe) + * + * Called on an in-use pipe to clear anything being transmitted or received + * Lock must be held before calling this. + */ +static void reset_pipe(snd_dbri_t * dbri, int pipe) +{ + int sdp; + int desc; + volatile int *cmd; + + if (pipe < 0 || pipe > 31) { + printk("DBRI: reset_pipe called with illegal pipe number\n"); + return; + } + + sdp = dbri->pipes[pipe].sdp; + if (sdp == 0) { + printk("DBRI: reset_pipe called on uninitialized pipe\n"); + return; + } + + cmd = dbri_cmdlock(dbri, NoGetLock); + *(cmd++) = DBRI_CMD(D_SDP, 0, sdp | D_SDP_C | D_SDP_P); + *(cmd++) = 0; + dbri_cmdsend(dbri, cmd); + + desc = dbri->pipes[pipe].first_desc; + while (desc != -1) { + dbri->descs[desc].inuse = 0; + desc = dbri->descs[desc].next; + } + + dbri->pipes[pipe].desc = -1; + dbri->pipes[pipe].first_desc = -1; +} + +/* FIXME: direction as an argument? */ +static void setup_pipe(snd_dbri_t * dbri, int pipe, int sdp) +{ + if (pipe < 0 || pipe > 31) { + printk("DBRI: setup_pipe called with illegal pipe number\n"); + return; + } + + if ((sdp & 0xf800) != sdp) { + printk("DBRI: setup_pipe called with strange SDP value\n"); + /* sdp &= 0xf800; */ + } + + /* If this is a fixed receive pipe, arrange for an interrupt + * every time its data changes + */ + if (D_SDP_MODE(sdp) == D_SDP_FIXED && !(sdp & D_SDP_TO_SER)) + sdp |= D_SDP_CHANGE; + + sdp |= D_PIPE(pipe); + dbri->pipes[pipe].sdp = sdp; + dbri->pipes[pipe].desc = -1; + dbri->pipes[pipe].first_desc = -1; + if (sdp & D_SDP_TO_SER) + dbri->pipes[pipe].direction = PIPEoutput; + else + dbri->pipes[pipe].direction = PIPEinput; + + reset_pipe(dbri, pipe); +} + +/* FIXME: direction not needed */ +static void link_time_slot(snd_dbri_t * dbri, int pipe, + enum in_or_out direction, int basepipe, + int length, int cycle) +{ + volatile s32 *cmd; + int val; + int prevpipe; + int nextpipe; + + if (pipe < 0 || pipe > 31 || basepipe < 0 || basepipe > 31) { + printk + ("DBRI: link_time_slot called with illegal pipe number\n"); + return; + } + + if (dbri->pipes[pipe].sdp == 0 || dbri->pipes[basepipe].sdp == 0) { + printk("DBRI: link_time_slot called on uninitialized pipe\n"); + return; + } + + /* Deal with CHI special case: + * "If transmission on edges 0 or 1 is desired, then cycle n + * (where n = # of bit times per frame...) must be used." + * - DBRI data sheet, page 11 + */ + if (basepipe == 16 && direction == PIPEoutput && cycle == 0) + cycle = dbri->chi_bpf; + + if (basepipe == pipe) { + prevpipe = pipe; + nextpipe = pipe; + } else { + /* We're not initializing a new linked list (basepipe != pipe), + * so run through the linked list and find where this pipe + * should be sloted in, based on its cycle. CHI confuses + * things a bit, since it has a single anchor for both its + * transmit and receive lists. + */ + if (basepipe == 16) { + if (direction == PIPEinput) { + prevpipe = dbri->chi_in_pipe; + } else { + prevpipe = dbri->chi_out_pipe; + } + } else { + prevpipe = basepipe; + } + + nextpipe = dbri->pipes[prevpipe].nextpipe; + + while (dbri->pipes[nextpipe].cycle < cycle + && dbri->pipes[nextpipe].nextpipe != basepipe) { + prevpipe = nextpipe; + nextpipe = dbri->pipes[nextpipe].nextpipe; + } + } + + if (prevpipe == 16) { + if (direction == PIPEinput) { + dbri->chi_in_pipe = pipe; + } else { + dbri->chi_out_pipe = pipe; + } + } else { + dbri->pipes[prevpipe].nextpipe = pipe; + } + + dbri->pipes[pipe].nextpipe = nextpipe; + dbri->pipes[pipe].cycle = cycle; + dbri->pipes[pipe].length = length; + + cmd = dbri_cmdlock(dbri, NoGetLock); + + if (direction == PIPEinput) { + val = D_DTS_VI | D_DTS_INS | D_DTS_PRVIN(prevpipe) | pipe; + *(cmd++) = DBRI_CMD(D_DTS, 0, val); + *(cmd++) = + D_TS_LEN(length) | D_TS_CYCLE(cycle) | D_TS_NEXT(nextpipe); + *(cmd++) = 0; + } else { + val = D_DTS_VO | D_DTS_INS | D_DTS_PRVOUT(prevpipe) | pipe; + *(cmd++) = DBRI_CMD(D_DTS, 0, val); + *(cmd++) = 0; + *(cmd++) = + D_TS_LEN(length) | D_TS_CYCLE(cycle) | D_TS_NEXT(nextpipe); + } + + dbri_cmdsend(dbri, cmd); +} + +static void unlink_time_slot(snd_dbri_t * dbri, int pipe, + enum in_or_out direction, int prevpipe, + int nextpipe) +{ + volatile s32 *cmd; + int val; + + if (pipe < 0 || pipe > 31 || prevpipe < 0 || prevpipe > 31) { + printk + ("DBRI: unlink_time_slot called with illegal pipe number\n"); + return; + } + + cmd = dbri_cmdlock(dbri, NoGetLock); + + if (direction == PIPEinput) { + val = D_DTS_VI | D_DTS_DEL | D_DTS_PRVIN(prevpipe) | pipe; + *(cmd++) = DBRI_CMD(D_DTS, 0, val); + *(cmd++) = D_TS_NEXT(nextpipe); + *(cmd++) = 0; + } else { + val = D_DTS_VO | D_DTS_DEL | D_DTS_PRVOUT(prevpipe) | pipe; + *(cmd++) = DBRI_CMD(D_DTS, 0, val); + *(cmd++) = 0; + *(cmd++) = D_TS_NEXT(nextpipe); + } + + dbri_cmdsend(dbri, cmd); +} + +/* xmit_fixed() / recv_fixed() + * + * Transmit/receive data on a "fixed" pipe - i.e, one whose contents are not + * expected to change much, and which we don't need to buffer. + * The DBRI only interrupts us when the data changes (receive pipes), + * or only changes the data when this function is called (transmit pipes). + * Only short pipes (numbers 16-31) can be used in fixed data mode. + * + * These function operate on a 32-bit field, no matter how large + * the actual time slot is. The interrupt handler takes care of bit + * ordering and alignment. An 8-bit time slot will always end up + * in the low-order 8 bits, filled either MSB-first or LSB-first, + * depending on the settings passed to setup_pipe() + */ +static void xmit_fixed(snd_dbri_t * dbri, int pipe, unsigned int data) +{ + volatile s32 *cmd; + + if (pipe < 16 || pipe > 31) { + printk("DBRI: xmit_fixed: Illegal pipe number\n"); + return; + } + + if (D_SDP_MODE(dbri->pipes[pipe].sdp) == 0) { + printk("DBRI: xmit_fixed: Uninitialized pipe %d\n", pipe); + return; + } + + if (D_SDP_MODE(dbri->pipes[pipe].sdp) != D_SDP_FIXED) { + printk("DBRI: xmit_fixed: Non-fixed pipe %d\n", pipe); + return; + } + + if (!(dbri->pipes[pipe].sdp & D_SDP_TO_SER)) { + printk("DBRI: xmit_fixed: Called on receive pipe %d\n", pipe); + return; + } + + /* DBRI short pipes always transmit LSB first */ + + if (dbri->pipes[pipe].sdp & D_SDP_MSB) + data = reverse_bytes(data, dbri->pipes[pipe].length); + + cmd = dbri_cmdlock(dbri, GetLock); + + *(cmd++) = DBRI_CMD(D_SSP, 0, pipe); + *(cmd++) = data; + + dbri_cmdsend(dbri, cmd); +} + +static void recv_fixed(snd_dbri_t * dbri, int pipe, volatile __u32 * ptr) +{ + if (pipe < 16 || pipe > 31) { + printk("DBRI: recv_fixed called with illegal pipe number\n"); + return; + } + + if (D_SDP_MODE(dbri->pipes[pipe].sdp) != D_SDP_FIXED) { + printk("DBRI: recv_fixed called on non-fixed pipe %d\n", pipe); + return; + } + + if (dbri->pipes[pipe].sdp & D_SDP_TO_SER) { + printk("DBRI: recv_fixed called on transmit pipe %d\n", pipe); + return; + } + + dbri->pipes[pipe].recv_fixed_ptr = ptr; +} + +/* setup_descs() + * + * Setup transmit/receive data on a "long" pipe - i.e, one associated + * with a DMA buffer. + * + * Only pipe numbers 0-15 can be used in this mode. + * + * This function takes a stream number pointing to a data buffer, + * and work by building chains of descriptors which identify the + * data buffers. Buffers too large for a single descriptor will + * be spread across multiple descriptors. + */ +static int setup_descs(snd_dbri_t * dbri, int streamno, unsigned int period) +{ + dbri_streaminfo_t *info = &dbri->stream_info[streamno]; + __u32 dvma_buffer; + int desc = 0; + int len; + int first_desc = -1; + int last_desc = -1; + + if (info->pipe < 0 || info->pipe > 15) { + printk("DBRI: setup_descs: Illegal pipe number\n"); + return -2; + } + + if (dbri->pipes[info->pipe].sdp == 0) { + printk("DBRI: setup_descs: Uninitialized pipe %d\n", + info->pipe); + return -2; + } + + dvma_buffer = info->dvma_buffer; + len = info->size; + + if (streamno == DBRI_PLAY) { + if (!(dbri->pipes[info->pipe].sdp & D_SDP_TO_SER)) { + printk("DBRI: setup_descs: Called on receive pipe %d\n", + info->pipe); + return -2; + } + } else { + if (dbri->pipes[info->pipe].sdp & D_SDP_TO_SER) { + printk + ("DBRI: setup_descs: Called on transmit pipe %d\n", + info->pipe); + return -2; + } + /* Should be able to queue multiple buffers to receive on a pipe */ + if (pipe_active(dbri, info->pipe)) { + printk("DBRI: recv_on_pipe: Called on active pipe %d\n", + info->pipe); + return -2; + } + + /* Make sure buffer size is multiple of four */ + len &= ~3; + } + + while (len > 0) { + int mylen; + + for (; desc < DBRI_NO_DESCS; desc++) { + if (!dbri->descs[desc].inuse) + break; + } + if (desc == DBRI_NO_DESCS) { + printk("DBRI: setup_descs: No descriptors\n"); + return -1; + } + + if (len > DBRI_TD_MAXCNT) { + mylen = DBRI_TD_MAXCNT; /* 8KB - 1 */ + } else { + mylen = len; + } + if (mylen > period) { + mylen = period; + } + + dbri->descs[desc].inuse = 1; + dbri->descs[desc].next = -1; + dbri->dma->desc[desc].ba = dvma_buffer; + dbri->dma->desc[desc].nda = 0; + + if (streamno == DBRI_PLAY) { + dbri->descs[desc].len = mylen; + dbri->dma->desc[desc].word1 = DBRI_TD_CNT(mylen); + dbri->dma->desc[desc].word4 = 0; + if (first_desc != -1) + dbri->dma->desc[desc].word1 |= DBRI_TD_M; + } else { + dbri->descs[desc].len = 0; + dbri->dma->desc[desc].word1 = 0; + dbri->dma->desc[desc].word4 = + DBRI_RD_B | DBRI_RD_BCNT(mylen); + } + + if (first_desc == -1) { + first_desc = desc; + } else { + dbri->descs[last_desc].next = desc; + dbri->dma->desc[last_desc].nda = + dbri->dma_dvma + dbri_dma_off(desc, desc); + } + + last_desc = desc; + dvma_buffer += mylen; + len -= mylen; + } + + if (first_desc == -1 || last_desc == -1) { + printk("DBRI: setup_descs: Not enough descriptors available\n"); + return -1; + } + + dbri->dma->desc[last_desc].word1 &= ~DBRI_TD_M; + if (streamno == DBRI_PLAY) { + dbri->dma->desc[last_desc].word1 |= + DBRI_TD_I | DBRI_TD_F | DBRI_TD_B; + } + dbri->pipes[info->pipe].first_desc = first_desc; + dbri->pipes[info->pipe].desc = first_desc; + + for (desc = first_desc; desc != -1; desc = dbri->descs[desc].next) { + dprintk(D_DESC, "DESC %d: %08x %08x %08x %08x\n", + desc, + dbri->dma->desc[desc].word1, + dbri->dma->desc[desc].ba, + dbri->dma->desc[desc].nda, dbri->dma->desc[desc].word4); + } + return 0; +} + +/* +**************************************************************************** +************************** DBRI - CHI interface **************************** +**************************************************************************** + +The CHI is a four-wire (clock, frame sync, data in, data out) time-division +multiplexed serial interface which the DBRI can operate in either master +(give clock/frame sync) or slave (take clock/frame sync) mode. + +*/ + +enum master_or_slave { CHImaster, CHIslave }; + +static void reset_chi(snd_dbri_t * dbri, enum master_or_slave master_or_slave, + int bits_per_frame) +{ + volatile s32 *cmd; + int val; + static int chi_initialized = 0; /* FIXME: mutex? */ + + if (!chi_initialized) { + + cmd = dbri_cmdlock(dbri, GetLock); + + /* Set CHI Anchor: Pipe 16 */ + + val = D_DTS_VI | D_DTS_INS | D_DTS_PRVIN(16) | D_PIPE(16); + *(cmd++) = DBRI_CMD(D_DTS, 0, val); + *(cmd++) = D_TS_ANCHOR | D_TS_NEXT(16); + *(cmd++) = 0; + + val = D_DTS_VO | D_DTS_INS | D_DTS_PRVOUT(16) | D_PIPE(16); + *(cmd++) = DBRI_CMD(D_DTS, 0, val); + *(cmd++) = 0; + *(cmd++) = D_TS_ANCHOR | D_TS_NEXT(16); + + dbri->pipes[16].sdp = 1; + dbri->pipes[16].nextpipe = 16; + dbri->chi_in_pipe = 16; + dbri->chi_out_pipe = 16; + +#if 0 + chi_initialized++; +#endif + } else { + int pipe; + + for (pipe = dbri->chi_in_pipe; + pipe != 16; pipe = dbri->pipes[pipe].nextpipe) { + unlink_time_slot(dbri, pipe, PIPEinput, + 16, dbri->pipes[pipe].nextpipe); + } + for (pipe = dbri->chi_out_pipe; + pipe != 16; pipe = dbri->pipes[pipe].nextpipe) { + unlink_time_slot(dbri, pipe, PIPEoutput, + 16, dbri->pipes[pipe].nextpipe); + } + + dbri->chi_in_pipe = 16; + dbri->chi_out_pipe = 16; + + cmd = dbri_cmdlock(dbri, GetLock); + } + + if (master_or_slave == CHIslave) { + /* Setup DBRI for CHI Slave - receive clock, frame sync (FS) + * + * CHICM = 0 (slave mode, 8 kHz frame rate) + * IR = give immediate CHI status interrupt + * EN = give CHI status interrupt upon change + */ + *(cmd++) = DBRI_CMD(D_CHI, 0, D_CHI_CHICM(0)); + } else { + /* Setup DBRI for CHI Master - generate clock, FS + * + * BPF = bits per 8 kHz frame + * 12.288 MHz / CHICM_divisor = clock rate + * FD = 1 - drive CHIFS on rising edge of CHICK + */ + int clockrate = bits_per_frame * 8; + int divisor = 12288 / clockrate; + + if (divisor > 255 || divisor * clockrate != 12288) + printk("DBRI: illegal bits_per_frame in setup_chi\n"); + + *(cmd++) = DBRI_CMD(D_CHI, 0, D_CHI_CHICM(divisor) | D_CHI_FD + | D_CHI_BPF(bits_per_frame)); + } + + dbri->chi_bpf = bits_per_frame; + + /* CHI Data Mode + * + * RCE = 0 - receive on falling edge of CHICK + * XCE = 1 - transmit on rising edge of CHICK + * XEN = 1 - enable transmitter + * REN = 1 - enable receiver + */ + + *(cmd++) = DBRI_CMD(D_PAUSE, 0, 0); + *(cmd++) = DBRI_CMD(D_CDM, 0, D_CDM_XCE | D_CDM_XEN | D_CDM_REN); + + dbri_cmdsend(dbri, cmd); +} + +/* +**************************************************************************** +*********************** CS4215 audio codec management ********************** +**************************************************************************** + +In the standard SPARC audio configuration, the CS4215 codec is attached +to the DBRI via the CHI interface and few of the DBRI's PIO pins. + +*/ +static void cs4215_setup_pipes(snd_dbri_t * dbri) +{ + /* + * Data mode: + * Pipe 4: Send timeslots 1-4 (audio data) + * Pipe 20: Send timeslots 5-8 (part of ctrl data) + * Pipe 6: Receive timeslots 1-4 (audio data) + * Pipe 21: Receive timeslots 6-7. We can only receive 20 bits via + * interrupt, and the rest of the data (slot 5 and 8) is + * not relevant for us (only for doublechecking). + * + * Control mode: + * Pipe 17: Send timeslots 1-4 (slots 5-8 are readonly) + * Pipe 18: Receive timeslot 1 (clb). + * Pipe 19: Receive timeslot 7 (version). + */ + + setup_pipe(dbri, 4, D_SDP_MEM | D_SDP_TO_SER | D_SDP_MSB); + setup_pipe(dbri, 20, D_SDP_FIXED | D_SDP_TO_SER | D_SDP_MSB); + setup_pipe(dbri, 6, D_SDP_MEM | D_SDP_FROM_SER | D_SDP_MSB); + setup_pipe(dbri, 21, D_SDP_FIXED | D_SDP_FROM_SER | D_SDP_MSB); + + setup_pipe(dbri, 17, D_SDP_FIXED | D_SDP_TO_SER | D_SDP_MSB); + setup_pipe(dbri, 18, D_SDP_FIXED | D_SDP_FROM_SER | D_SDP_MSB); + setup_pipe(dbri, 19, D_SDP_FIXED | D_SDP_FROM_SER | D_SDP_MSB); +} + +static int cs4215_init_data(struct cs4215 *mm) +{ + /* + * No action, memory resetting only. + * + * Data Time Slot 5-8 + * Speaker,Line and Headphone enable. Gain set to the half. + * Input is mike. + */ + mm->data[0] = CS4215_LO(0x20) | CS4215_HE | CS4215_LE; + mm->data[1] = CS4215_RO(0x20) | CS4215_SE; + mm->data[2] = CS4215_LG(0x8) | CS4215_IS | CS4215_PIO0 | CS4215_PIO1; + mm->data[3] = CS4215_RG(0x8) | CS4215_MA(0xf); + + /* + * Control Time Slot 1-4 + * 0: Default I/O voltage scale + * 1: 8 bit ulaw, 8kHz, mono, high pass filter disabled + * 2: Serial enable, CHI master, 128 bits per frame, clock 1 + * 3: Tests disabled + */ + mm->ctrl[0] = CS4215_RSRVD_1 | CS4215_MLB; + mm->ctrl[1] = CS4215_DFR_ULAW | CS4215_FREQ[0].csval; + mm->ctrl[2] = CS4215_XCLK | CS4215_BSEL_128 | CS4215_FREQ[0].xtal; + mm->ctrl[3] = 0; + + mm->status = 0; + mm->version = 0xff; + mm->precision = 8; /* For ULAW */ + mm->channels = 2; + + return 0; +} + +static void cs4215_setdata(snd_dbri_t * dbri, int muted) +{ + if (muted) { + dbri->mm.data[0] |= 63; + dbri->mm.data[1] |= 63; + dbri->mm.data[2] &= ~15; + dbri->mm.data[3] &= ~15; + } else { + /* Start by setting the playback attenuation. */ + dbri_streaminfo_t *info = &dbri->stream_info[DBRI_PLAY]; + int left_gain = info->left_gain % 64; + int right_gain = info->right_gain % 64; + + if (info->balance < DBRI_MID_BALANCE) { + right_gain *= info->balance; + right_gain /= DBRI_MID_BALANCE; + } else { + left_gain *= DBRI_RIGHT_BALANCE - info->balance; + left_gain /= DBRI_MID_BALANCE; + } + + dbri->mm.data[0] &= ~0x3f; /* Reset the volume bits */ + dbri->mm.data[1] &= ~0x3f; + dbri->mm.data[0] |= (DBRI_MAX_VOLUME - left_gain); + dbri->mm.data[1] |= (DBRI_MAX_VOLUME - right_gain); + + /* Now set the recording gain. */ + info = &dbri->stream_info[DBRI_REC]; + left_gain = info->left_gain % 16; + right_gain = info->right_gain % 16; + dbri->mm.data[2] |= CS4215_LG(left_gain); + dbri->mm.data[3] |= CS4215_RG(right_gain); + } + + xmit_fixed(dbri, 20, *(int *)dbri->mm.data); +} + +/* + * Set the CS4215 to data mode. + */ +static void cs4215_open(snd_dbri_t * dbri) +{ + int data_width; + u32 tmp; + + dprintk(D_MM, "cs4215_open: %d channels, %d bits\n", + dbri->mm.channels, dbri->mm.precision); + + /* Temporarily mute outputs, and wait 1/8000 sec (125 us) + * to make sure this takes. This avoids clicking noises. + */ + + cs4215_setdata(dbri, 1); + udelay(125); + + /* + * Data mode: + * Pipe 4: Send timeslots 1-4 (audio data) + * Pipe 20: Send timeslots 5-8 (part of ctrl data) + * Pipe 6: Receive timeslots 1-4 (audio data) + * Pipe 21: Receive timeslots 6-7. We can only receive 20 bits via + * interrupt, and the rest of the data (slot 5 and 8) is + * not relevant for us (only for doublechecking). + * + * Just like in control mode, the time slots are all offset by eight + * bits. The CS4215, it seems, observes TSIN (the delayed signal) + * even if it's the CHI master. Don't ask me... + */ + tmp = sbus_readl(dbri->regs + REG0); + tmp &= ~(D_C); /* Disable CHI */ + sbus_writel(tmp, dbri->regs + REG0); + + /* Switch CS4215 to data mode - set PIO3 to 1 */ + sbus_writel(D_ENPIO | D_PIO1 | D_PIO3 | + (dbri->mm.onboard ? D_PIO0 : D_PIO2), dbri->regs + REG2); + + reset_chi(dbri, CHIslave, 128); + + /* Note: this next doesn't work for 8-bit stereo, because the two + * channels would be on timeslots 1 and 3, with 2 and 4 idle. + * (See CS4215 datasheet Fig 15) + * + * DBRI non-contiguous mode would be required to make this work. + */ + data_width = dbri->mm.channels * dbri->mm.precision; + + link_time_slot(dbri, 20, PIPEoutput, 16, 32, dbri->mm.offset + 32); + link_time_slot(dbri, 4, PIPEoutput, 16, data_width, dbri->mm.offset); + link_time_slot(dbri, 6, PIPEinput, 16, data_width, dbri->mm.offset); + link_time_slot(dbri, 21, PIPEinput, 16, 16, dbri->mm.offset + 40); + + /* FIXME: enable CHI after _setdata? */ + tmp = sbus_readl(dbri->regs + REG0); + tmp |= D_C; /* Enable CHI */ + sbus_writel(tmp, dbri->regs + REG0); + + cs4215_setdata(dbri, 0); +} + +/* + * Send the control information (i.e. audio format) + */ +static int cs4215_setctrl(snd_dbri_t * dbri) +{ + int i, val; + u32 tmp; + + /* FIXME - let the CPU do something useful during these delays */ + + /* Temporarily mute outputs, and wait 1/8000 sec (125 us) + * to make sure this takes. This avoids clicking noises. + */ + + cs4215_setdata(dbri, 1); + udelay(125); + + /* + * Enable Control mode: Set DBRI's PIO3 (4215's D/~C) to 0, then wait + * 12 cycles <= 12/(5512.5*64) sec = 34.01 usec + */ + val = D_ENPIO | D_PIO1 | (dbri->mm.onboard ? D_PIO0 : D_PIO2); + sbus_writel(val, dbri->regs + REG2); + dprintk(D_MM, "cs4215_setctrl: reg2=0x%x\n", val); + udelay(34); + + /* In Control mode, the CS4215 is a slave device, so the DBRI must + * operate as CHI master, supplying clocking and frame synchronization. + * + * In Data mode, however, the CS4215 must be CHI master to insure + * that its data stream is synchronous with its codec. + * + * The upshot of all this? We start by putting the DBRI into master + * mode, program the CS4215 in Control mode, then switch the CS4215 + * into Data mode and put the DBRI into slave mode. Various timing + * requirements must be observed along the way. + * + * Oh, and one more thing, on a SPARCStation 20 (and maybe + * others?), the addressing of the CS4215's time slots is + * offset by eight bits, so we add eight to all the "cycle" + * values in the Define Time Slot (DTS) commands. This is + * done in hardware by a TI 248 that delays the DBRI->4215 + * frame sync signal by eight clock cycles. Anybody know why? + */ + tmp = sbus_readl(dbri->regs + REG0); + tmp &= ~D_C; /* Disable CHI */ + sbus_writel(tmp, dbri->regs + REG0); + + reset_chi(dbri, CHImaster, 128); + + /* + * Control mode: + * Pipe 17: Send timeslots 1-4 (slots 5-8 are readonly) + * Pipe 18: Receive timeslot 1 (clb). + * Pipe 19: Receive timeslot 7 (version). + */ + + link_time_slot(dbri, 17, PIPEoutput, 16, 32, dbri->mm.offset); + link_time_slot(dbri, 18, PIPEinput, 16, 8, dbri->mm.offset); + link_time_slot(dbri, 19, PIPEinput, 16, 8, dbri->mm.offset + 48); + + /* Wait for the chip to echo back CLB (Control Latch Bit) as zero */ + dbri->mm.ctrl[0] &= ~CS4215_CLB; + xmit_fixed(dbri, 17, *(int *)dbri->mm.ctrl); + + tmp = sbus_readl(dbri->regs + REG0); + tmp |= D_C; /* Enable CHI */ + sbus_writel(tmp, dbri->regs + REG0); + + for (i = 64; ((dbri->mm.status & 0xe4) != 0x20); --i) { + udelay(125); + } + if (i == 0) { + dprintk(D_MM, "CS4215 didn't respond to CLB (0x%02x)\n", + dbri->mm.status); + return -1; + } + + /* Disable changes to our copy of the version number, as we are about + * to leave control mode. + */ + recv_fixed(dbri, 19, NULL); + + /* Terminate CS4215 control mode - data sheet says + * "Set CLB=1 and send two more frames of valid control info" + */ + dbri->mm.ctrl[0] |= CS4215_CLB; + xmit_fixed(dbri, 17, *(int *)dbri->mm.ctrl); + + /* Two frames of control info @ 8kHz frame rate = 250 us delay */ + udelay(250); + + cs4215_setdata(dbri, 0); + + return 0; +} + +/* + * Setup the codec with the sampling rate, audio format and number of + * channels. + * As part of the process we resend the settings for the data + * timeslots as well. + */ +static int cs4215_prepare(snd_dbri_t * dbri, unsigned int rate, + snd_pcm_format_t format, unsigned int channels) +{ + int freq_idx; + int ret = 0; + + /* Lookup index for this rate */ + for (freq_idx = 0; CS4215_FREQ[freq_idx].freq != 0; freq_idx++) { + if (CS4215_FREQ[freq_idx].freq == rate) + break; + } + if (CS4215_FREQ[freq_idx].freq != rate) { + printk(KERN_WARNING "DBRI: Unsupported rate %d Hz\n", rate); + return -1; + } + + switch (format) { + case SNDRV_PCM_FORMAT_MU_LAW: + dbri->mm.ctrl[1] = CS4215_DFR_ULAW; + dbri->mm.precision = 8; + break; + case SNDRV_PCM_FORMAT_A_LAW: + dbri->mm.ctrl[1] = CS4215_DFR_ALAW; + dbri->mm.precision = 8; + break; + case SNDRV_PCM_FORMAT_U8: + dbri->mm.ctrl[1] = CS4215_DFR_LINEAR8; + dbri->mm.precision = 8; + break; + case SNDRV_PCM_FORMAT_S16_BE: + dbri->mm.ctrl[1] = CS4215_DFR_LINEAR16; + dbri->mm.precision = 16; + break; + default: + printk(KERN_WARNING "DBRI: Unsupported format %d\n", format); + return -1; + } + + /* Add rate parameters */ + dbri->mm.ctrl[1] |= CS4215_FREQ[freq_idx].csval; + dbri->mm.ctrl[2] = CS4215_XCLK | + CS4215_BSEL_128 | CS4215_FREQ[freq_idx].xtal; + + dbri->mm.channels = channels; + /* Stereo bit: 8 bit stereo not working yet. */ + if ((channels > 1) && (dbri->mm.precision == 16)) + dbri->mm.ctrl[1] |= CS4215_DFR_STEREO; + + ret = cs4215_setctrl(dbri); + if (ret == 0) + cs4215_open(dbri); /* set codec to data mode */ + + return ret; +} + +/* + * + */ +static int cs4215_init(snd_dbri_t * dbri) +{ + u32 reg2 = sbus_readl(dbri->regs + REG2); + dprintk(D_MM, "cs4215_init: reg2=0x%x\n", reg2); + + /* Look for the cs4215 chips */ + if (reg2 & D_PIO2) { + dprintk(D_MM, "Onboard CS4215 detected\n"); + dbri->mm.onboard = 1; + } + if (reg2 & D_PIO0) { + dprintk(D_MM, "Speakerbox detected\n"); + dbri->mm.onboard = 0; + + if (reg2 & D_PIO2) { + printk(KERN_INFO "DBRI: Using speakerbox / " + "ignoring onboard mmcodec.\n"); + sbus_writel(D_ENPIO2, dbri->regs + REG2); + } + } + + if (!(reg2 & (D_PIO0 | D_PIO2))) { + printk(KERN_ERR "DBRI: no mmcodec found.\n"); + return -EIO; + } + + cs4215_setup_pipes(dbri); + + cs4215_init_data(&dbri->mm); + + /* Enable capture of the status & version timeslots. */ + recv_fixed(dbri, 18, &dbri->mm.status); + recv_fixed(dbri, 19, &dbri->mm.version); + + dbri->mm.offset = dbri->mm.onboard ? 0 : 8; + if (cs4215_setctrl(dbri) == -1 || dbri->mm.version == 0xff) { + dprintk(D_MM, "CS4215 failed probe at offset %d\n", + dbri->mm.offset); + return -EIO; + } + dprintk(D_MM, "Found CS4215 at offset %d\n", dbri->mm.offset); + + return 0; +} + +/* +**************************************************************************** +*************************** DBRI interrupt handler ************************* +**************************************************************************** + +The DBRI communicates with the CPU mainly via a circular interrupt +buffer. When an interrupt is signaled, the CPU walks through the +buffer and calls dbri_process_one_interrupt() for each interrupt word. +Complicated interrupts are handled by dedicated functions (which +appear first in this file). Any pending interrupts can be serviced by +calling dbri_process_interrupt_buffer(), which works even if the CPU's +interrupts are disabled. This function is used by dbri_cmdsend() +to make sure we're synced up with the chip after each command sequence, +even if we're running cli'ed. + +*/ + +/* xmit_descs() + * + * Transmit the current TD's for recording/playing, if needed. + * For playback, ALSA has filled the DMA memory with new data (we hope). + */ +static void xmit_descs(unsigned long data) +{ + snd_dbri_t *dbri = (snd_dbri_t *) data; + dbri_streaminfo_t *info; + volatile s32 *cmd; + unsigned long flags; + int first_td; + + if (dbri == NULL) + return; /* Disabled */ + + /* First check the recording stream for buffer overflow */ + info = &dbri->stream_info[DBRI_REC]; + spin_lock_irqsave(&dbri->lock, flags); + + if ((info->left >= info->size) && (info->pipe >= 0)) { + first_td = dbri->pipes[info->pipe].first_desc; + + dprintk(D_DESC, "xmit_descs rec @ TD %d\n", first_td); + + /* Stream could be closed by the time we run. */ + if (first_td < 0) { + goto play; + } + + cmd = dbri_cmdlock(dbri, NoGetLock); + *(cmd++) = DBRI_CMD(D_SDP, 0, + dbri->pipes[info->pipe].sdp + | D_SDP_P | D_SDP_EVERY | D_SDP_C); + *(cmd++) = dbri->dma_dvma + dbri_dma_off(desc, first_td); + dbri_cmdsend(dbri, cmd); + + /* Reset our admin of the pipe & bytes read. */ + dbri->pipes[info->pipe].desc = first_td; + info->left = 0; + } + +play: + spin_unlock_irqrestore(&dbri->lock, flags); + + /* Now check the playback stream for buffer underflow */ + info = &dbri->stream_info[DBRI_PLAY]; + spin_lock_irqsave(&dbri->lock, flags); + + if ((info->left <= 0) && (info->pipe >= 0)) { + first_td = dbri->pipes[info->pipe].first_desc; + + dprintk(D_DESC, "xmit_descs play @ TD %d\n", first_td); + + /* Stream could be closed by the time we run. */ + if (first_td < 0) { + spin_unlock_irqrestore(&dbri->lock, flags); + return; + } + + cmd = dbri_cmdlock(dbri, NoGetLock); + *(cmd++) = DBRI_CMD(D_SDP, 0, + dbri->pipes[info->pipe].sdp + | D_SDP_P | D_SDP_EVERY | D_SDP_C); + *(cmd++) = dbri->dma_dvma + dbri_dma_off(desc, first_td); + dbri_cmdsend(dbri, cmd); + + /* Reset our admin of the pipe & bytes written. */ + dbri->pipes[info->pipe].desc = first_td; + info->left = info->size; + } + spin_unlock_irqrestore(&dbri->lock, flags); +} + +DECLARE_TASKLET(xmit_descs_task, xmit_descs, 0); + +/* transmission_complete_intr() + * + * Called by main interrupt handler when DBRI signals transmission complete + * on a pipe (interrupt triggered by the B bit in a transmit descriptor). + * + * Walks through the pipe's list of transmit buffer descriptors, releasing + * each one's DMA buffer (if present), flagging the descriptor available, + * and signaling its callback routine (if present), before proceeding + * to the next one. Stops when the first descriptor is found without + * TBC (Transmit Buffer Complete) set, or we've run through them all. + */ + +static void transmission_complete_intr(snd_dbri_t * dbri, int pipe) +{ + dbri_streaminfo_t *info; + int td; + int status; + + info = &dbri->stream_info[DBRI_PLAY]; + + td = dbri->pipes[pipe].desc; + while (td >= 0) { + if (td >= DBRI_NO_DESCS) { + printk(KERN_ERR "DBRI: invalid td on pipe %d\n", pipe); + return; + } + + status = DBRI_TD_STATUS(dbri->dma->desc[td].word4); + if (!(status & DBRI_TD_TBC)) { + break; + } + + dprintk(D_INT, "TD %d, status 0x%02x\n", td, status); + + dbri->dma->desc[td].word4 = 0; /* Reset it for next time. */ + info->offset += dbri->descs[td].len; + info->left -= dbri->descs[td].len; + + /* On the last TD, transmit them all again. */ + if (dbri->descs[td].next == -1) { + if (info->left > 0) { + printk(KERN_WARNING + "%d bytes left after last transfer.\n", + info->left); + info->left = 0; + } + tasklet_schedule(&xmit_descs_task); + } + + td = dbri->descs[td].next; + dbri->pipes[pipe].desc = td; + } + + /* Notify ALSA */ + if (spin_is_locked(&dbri->lock)) { + spin_unlock(&dbri->lock); + snd_pcm_period_elapsed(info->substream); + spin_lock(&dbri->lock); + } else + snd_pcm_period_elapsed(info->substream); +} + +static void reception_complete_intr(snd_dbri_t * dbri, int pipe) +{ + dbri_streaminfo_t *info; + int rd = dbri->pipes[pipe].desc; + s32 status; + + if (rd < 0 || rd >= DBRI_NO_DESCS) { + printk(KERN_ERR "DBRI: invalid rd on pipe %d\n", pipe); + return; + } + + dbri->descs[rd].inuse = 0; + dbri->pipes[pipe].desc = dbri->descs[rd].next; + status = dbri->dma->desc[rd].word1; + dbri->dma->desc[rd].word1 = 0; /* Reset it for next time. */ + + info = &dbri->stream_info[DBRI_REC]; + info->offset += DBRI_RD_CNT(status); + info->left += DBRI_RD_CNT(status); + + /* FIXME: Check status */ + + dprintk(D_INT, "Recv RD %d, status 0x%02x, len %d\n", + rd, DBRI_RD_STATUS(status), DBRI_RD_CNT(status)); + + /* On the last TD, transmit them all again. */ + if (dbri->descs[rd].next == -1) { + if (info->left > info->size) { + printk(KERN_WARNING + "%d bytes recorded in %d size buffer.\n", + info->left, info->size); + } + tasklet_schedule(&xmit_descs_task); + } + + /* Notify ALSA */ + if (spin_is_locked(&dbri->lock)) { + spin_unlock(&dbri->lock); + snd_pcm_period_elapsed(info->substream); + spin_lock(&dbri->lock); + } else + snd_pcm_period_elapsed(info->substream); +} + +static void dbri_process_one_interrupt(snd_dbri_t * dbri, int x) +{ + int val = D_INTR_GETVAL(x); + int channel = D_INTR_GETCHAN(x); + int command = D_INTR_GETCMD(x); + int code = D_INTR_GETCODE(x); +#ifdef DBRI_DEBUG + int rval = D_INTR_GETRVAL(x); +#endif + + if (channel == D_INTR_CMD) { + dprintk(D_CMD, "INTR: Command: %-5s Value:%d\n", + cmds[command], val); + } else { + dprintk(D_INT, "INTR: Chan:%d Code:%d Val:%#x\n", + channel, code, rval); + } + + if (channel == D_INTR_CMD && command == D_WAIT) { + dbri->wait_seen++; + return; + } + + switch (code) { + case D_INTR_BRDY: + reception_complete_intr(dbri, channel); + break; + case D_INTR_XCMP: + case D_INTR_MINT: + transmission_complete_intr(dbri, channel); + break; + case D_INTR_UNDR: + /* UNDR - Transmission underrun + * resend SDP command with clear pipe bit (C) set + */ + { + volatile s32 *cmd; + + int pipe = channel; + int td = dbri->pipes[pipe].desc; + + dbri->dma->desc[td].word4 = 0; + cmd = dbri_cmdlock(dbri, NoGetLock); + *(cmd++) = DBRI_CMD(D_SDP, 0, + dbri->pipes[pipe].sdp + | D_SDP_P | D_SDP_C | D_SDP_2SAME); + *(cmd++) = dbri->dma_dvma + dbri_dma_off(desc, td); + dbri_cmdsend(dbri, cmd); + } + break; + case D_INTR_FXDT: + /* FXDT - Fixed data change */ + if (dbri->pipes[channel].sdp & D_SDP_MSB) + val = reverse_bytes(val, dbri->pipes[channel].length); + + if (dbri->pipes[channel].recv_fixed_ptr) + *(dbri->pipes[channel].recv_fixed_ptr) = val; + break; + default: + if (channel != D_INTR_CMD) + printk(KERN_WARNING + "DBRI: Ignored Interrupt: %d (0x%x)\n", code, x); + } +} + +/* dbri_process_interrupt_buffer advances through the DBRI's interrupt + * buffer until it finds a zero word (indicating nothing more to do + * right now). Non-zero words require processing and are handed off + * to dbri_process_one_interrupt AFTER advancing the pointer. This + * order is important since we might recurse back into this function + * and need to make sure the pointer has been advanced first. + */ +static void dbri_process_interrupt_buffer(snd_dbri_t * dbri) +{ + s32 x; + + while ((x = dbri->dma->intr[dbri->dbri_irqp]) != 0) { + dbri->dma->intr[dbri->dbri_irqp] = 0; + dbri->dbri_irqp++; + if (dbri->dbri_irqp == (DBRI_NO_INTS * DBRI_INT_BLK)) + dbri->dbri_irqp = 1; + else if ((dbri->dbri_irqp & (DBRI_INT_BLK - 1)) == 0) + dbri->dbri_irqp++; + + dbri_process_one_interrupt(dbri, x); + } +} + +static irqreturn_t snd_dbri_interrupt(int irq, void *dev_id, + struct pt_regs *regs) +{ + snd_dbri_t *dbri = dev_id; + static int errcnt = 0; + int x; + + if (dbri == NULL) + return IRQ_NONE; + spin_lock(&dbri->lock); + + /* + * Read it, so the interrupt goes away. + */ + x = sbus_readl(dbri->regs + REG1); + + if (x & (D_MRR | D_MLE | D_LBG | D_MBE)) { + u32 tmp; + + if (x & D_MRR) + printk(KERN_ERR + "DBRI: Multiple Error Ack on SBus reg1=0x%x\n", + x); + if (x & D_MLE) + printk(KERN_ERR + "DBRI: Multiple Late Error on SBus reg1=0x%x\n", + x); + if (x & D_LBG) + printk(KERN_ERR + "DBRI: Lost Bus Grant on SBus reg1=0x%x\n", x); + if (x & D_MBE) + printk(KERN_ERR + "DBRI: Burst Error on SBus reg1=0x%x\n", x); + + /* Some of these SBus errors cause the chip's SBus circuitry + * to be disabled, so just re-enable and try to keep going. + * + * The only one I've seen is MRR, which will be triggered + * if you let a transmit pipe underrun, then try to CDP it. + * + * If these things persist, we should probably reset + * and re-init the chip. + */ + if ((++errcnt) % 10 == 0) { + dprintk(D_INT, "Interrupt errors exceeded.\n"); + dbri_reset(dbri); + } else { + tmp = sbus_readl(dbri->regs + REG0); + tmp &= ~(D_D); + sbus_writel(tmp, dbri->regs + REG0); + } + } + + dbri_process_interrupt_buffer(dbri); + + /* FIXME: Write 0 into regs to ACK interrupt */ + + spin_unlock(&dbri->lock); + + return IRQ_HANDLED; +} + +/**************************************************************************** + PCM Interface +****************************************************************************/ +static snd_pcm_hardware_t snd_dbri_pcm_hw = { + .info = (SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP_VALID), + .formats = SNDRV_PCM_FMTBIT_MU_LAW | + SNDRV_PCM_FMTBIT_A_LAW | + SNDRV_PCM_FMTBIT_U8 | + SNDRV_PCM_FMTBIT_S16_BE, + .rates = SNDRV_PCM_RATE_8000_48000, + .rate_min = 8000, + .rate_max = 48000, + .channels_min = 1, + .channels_max = 2, + .buffer_bytes_max = (64 * 1024), + .period_bytes_min = 1, + .period_bytes_max = DBRI_TD_MAXCNT, + .periods_min = 1, + .periods_max = 1024, +}; + +static int snd_dbri_open(snd_pcm_substream_t * substream) +{ + snd_dbri_t *dbri = snd_pcm_substream_chip(substream); + snd_pcm_runtime_t *runtime = substream->runtime; + dbri_streaminfo_t *info = DBRI_STREAM(dbri, substream); + unsigned long flags; + + dprintk(D_USR, "open audio output.\n"); + runtime->hw = snd_dbri_pcm_hw; + + spin_lock_irqsave(&dbri->lock, flags); + info->substream = substream; + info->left = 0; + info->offset = 0; + info->dvma_buffer = 0; + info->pipe = -1; + spin_unlock_irqrestore(&dbri->lock, flags); + + cs4215_open(dbri); + + return 0; +} + +static int snd_dbri_close(snd_pcm_substream_t * substream) +{ + snd_dbri_t *dbri = snd_pcm_substream_chip(substream); + dbri_streaminfo_t *info = DBRI_STREAM(dbri, substream); + + dprintk(D_USR, "close audio output.\n"); + info->substream = NULL; + info->left = 0; + info->offset = 0; + + return 0; +} + +static int snd_dbri_hw_params(snd_pcm_substream_t * substream, + snd_pcm_hw_params_t * hw_params) +{ + snd_pcm_runtime_t *runtime = substream->runtime; + snd_dbri_t *dbri = snd_pcm_substream_chip(substream); + dbri_streaminfo_t *info = DBRI_STREAM(dbri, substream); + int direction; + int ret; + + /* set sampling rate, audio format and number of channels */ + ret = cs4215_prepare(dbri, params_rate(hw_params), + params_format(hw_params), + params_channels(hw_params)); + if (ret != 0) + return ret; + + if ((ret = snd_pcm_lib_malloc_pages(substream, + params_buffer_bytes(hw_params))) < 0) { + snd_printk(KERN_ERR "malloc_pages failed with %d\n", ret); + return ret; + } + + /* hw_params can get called multiple times. Only map the DMA once. + */ + if (info->dvma_buffer == 0) { + if (DBRI_STREAMNO(substream) == DBRI_PLAY) + direction = SBUS_DMA_TODEVICE; + else + direction = SBUS_DMA_FROMDEVICE; + + info->dvma_buffer = sbus_map_single(dbri->sdev, + runtime->dma_area, + params_buffer_bytes(hw_params), + direction); + } + + direction = params_buffer_bytes(hw_params); + dprintk(D_USR, "hw_params: %d bytes, dvma=%x\n", + direction, info->dvma_buffer); + return 0; +} + +static int snd_dbri_hw_free(snd_pcm_substream_t * substream) +{ + snd_dbri_t *dbri = snd_pcm_substream_chip(substream); + dbri_streaminfo_t *info = DBRI_STREAM(dbri, substream); + int direction; + dprintk(D_USR, "hw_free.\n"); + + /* hw_free can get called multiple times. Only unmap the DMA once. + */ + if (info->dvma_buffer) { + if (DBRI_STREAMNO(substream) == DBRI_PLAY) + direction = SBUS_DMA_TODEVICE; + else + direction = SBUS_DMA_FROMDEVICE; + + sbus_unmap_single(dbri->sdev, info->dvma_buffer, + substream->runtime->buffer_size, direction); + info->dvma_buffer = 0; + } + info->pipe = -1; + + return snd_pcm_lib_free_pages(substream); +} + +static int snd_dbri_prepare(snd_pcm_substream_t * substream) +{ + snd_dbri_t *dbri = snd_pcm_substream_chip(substream); + dbri_streaminfo_t *info = DBRI_STREAM(dbri, substream); + snd_pcm_runtime_t *runtime = substream->runtime; + int ret; + + info->size = snd_pcm_lib_buffer_bytes(substream); + if (DBRI_STREAMNO(substream) == DBRI_PLAY) + info->pipe = 4; /* Send pipe */ + else { + info->pipe = 6; /* Receive pipe */ + info->left = info->size; /* To trigger submittal */ + } + + spin_lock_irq(&dbri->lock); + + /* Setup the all the transmit/receive desciptors to cover the + * whole DMA buffer. + */ + ret = setup_descs(dbri, DBRI_STREAMNO(substream), + snd_pcm_lib_period_bytes(substream)); + + runtime->stop_threshold = DBRI_TD_MAXCNT / runtime->channels; + + spin_unlock_irq(&dbri->lock); + + dprintk(D_USR, "prepare audio output. %d bytes\n", info->size); + return ret; +} + +static int snd_dbri_trigger(snd_pcm_substream_t * substream, int cmd) +{ + snd_dbri_t *dbri = snd_pcm_substream_chip(substream); + dbri_streaminfo_t *info = DBRI_STREAM(dbri, substream); + int ret = 0; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + dprintk(D_USR, "start audio, period is %d bytes\n", + (int)snd_pcm_lib_period_bytes(substream)); + /* Enable & schedule the tasklet that re-submits the TDs. */ + xmit_descs_task.data = (unsigned long)dbri; + tasklet_schedule(&xmit_descs_task); + break; + case SNDRV_PCM_TRIGGER_STOP: + dprintk(D_USR, "stop audio.\n"); + /* Make the tasklet bail out immediately. */ + xmit_descs_task.data = 0; + reset_pipe(dbri, info->pipe); + break; + default: + ret = -EINVAL; + } + + return ret; +} + +static snd_pcm_uframes_t snd_dbri_pointer(snd_pcm_substream_t * substream) +{ + snd_dbri_t *dbri = snd_pcm_substream_chip(substream); + dbri_streaminfo_t *info = DBRI_STREAM(dbri, substream); + snd_pcm_uframes_t ret; + + ret = bytes_to_frames(substream->runtime, info->offset) + % substream->runtime->buffer_size; + dprintk(D_USR, "I/O pointer: %ld frames, %d bytes left.\n", + ret, info->left); + return ret; +} + +static snd_pcm_ops_t snd_dbri_ops = { + .open = snd_dbri_open, + .close = snd_dbri_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = snd_dbri_hw_params, + .hw_free = snd_dbri_hw_free, + .prepare = snd_dbri_prepare, + .trigger = snd_dbri_trigger, + .pointer = snd_dbri_pointer, +}; + +static int __devinit snd_dbri_pcm(snd_dbri_t * dbri) +{ + snd_pcm_t *pcm; + int err; + + if ((err = snd_pcm_new(dbri->card, + /* ID */ "sun_dbri", + /* device */ 0, + /* playback count */ 1, + /* capture count */ 1, &pcm)) < 0) + return err; + snd_assert(pcm != NULL, return -EINVAL); + + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_dbri_ops); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_dbri_ops); + + pcm->private_data = dbri; + pcm->info_flags = 0; + strcpy(pcm->name, dbri->card->shortname); + dbri->pcm = pcm; + + if ((err = snd_pcm_lib_preallocate_pages_for_all(pcm, + SNDRV_DMA_TYPE_CONTINUOUS, + snd_dma_continuous_data(GFP_KERNEL), + 64 * 1024, 64 * 1024)) < 0) { + return err; + } + + return 0; +} + +/***************************************************************************** + Mixer interface +*****************************************************************************/ + +static int snd_cs4215_info_volume(snd_kcontrol_t * kcontrol, + snd_ctl_elem_info_t * uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 2; + uinfo->value.integer.min = 0; + if (kcontrol->private_value == DBRI_PLAY) { + uinfo->value.integer.max = DBRI_MAX_VOLUME; + } else { + uinfo->value.integer.max = DBRI_MAX_GAIN; + } + return 0; +} + +static int snd_cs4215_get_volume(snd_kcontrol_t * kcontrol, + snd_ctl_elem_value_t * ucontrol) +{ + snd_dbri_t *dbri = snd_kcontrol_chip(kcontrol); + dbri_streaminfo_t *info; + snd_assert(dbri != NULL, return -EINVAL); + info = &dbri->stream_info[kcontrol->private_value]; + snd_assert(info != NULL, return -EINVAL); + + ucontrol->value.integer.value[0] = info->left_gain; + ucontrol->value.integer.value[1] = info->right_gain; + return 0; +} + +static int snd_cs4215_put_volume(snd_kcontrol_t * kcontrol, + snd_ctl_elem_value_t * ucontrol) +{ + snd_dbri_t *dbri = snd_kcontrol_chip(kcontrol); + dbri_streaminfo_t *info = &dbri->stream_info[kcontrol->private_value]; + unsigned long flags; + int changed = 0; + + if (info->left_gain != ucontrol->value.integer.value[0]) { + info->left_gain = ucontrol->value.integer.value[0]; + changed = 1; + } + if (info->right_gain != ucontrol->value.integer.value[1]) { + info->right_gain = ucontrol->value.integer.value[1]; + changed = 1; + } + if (changed == 1) { + /* First mute outputs, and wait 1/8000 sec (125 us) + * to make sure this takes. This avoids clicking noises. + */ + spin_lock_irqsave(&dbri->lock, flags); + + cs4215_setdata(dbri, 1); + udelay(125); + cs4215_setdata(dbri, 0); + + spin_unlock_irqrestore(&dbri->lock, flags); + } + return changed; +} + +static int snd_cs4215_info_single(snd_kcontrol_t * kcontrol, + snd_ctl_elem_info_t * uinfo) +{ + int mask = (kcontrol->private_value >> 16) & 0xff; + + uinfo->type = (mask == 1) ? + SNDRV_CTL_ELEM_TYPE_BOOLEAN : SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = mask; + return 0; +} + +static int snd_cs4215_get_single(snd_kcontrol_t * kcontrol, + snd_ctl_elem_value_t * ucontrol) +{ + snd_dbri_t *dbri = snd_kcontrol_chip(kcontrol); + int elem = kcontrol->private_value & 0xff; + int shift = (kcontrol->private_value >> 8) & 0xff; + int mask = (kcontrol->private_value >> 16) & 0xff; + int invert = (kcontrol->private_value >> 24) & 1; + snd_assert(dbri != NULL, return -EINVAL); + + if (elem < 4) { + ucontrol->value.integer.value[0] = + (dbri->mm.data[elem] >> shift) & mask; + } else { + ucontrol->value.integer.value[0] = + (dbri->mm.ctrl[elem - 4] >> shift) & mask; + } + + if (invert == 1) { + ucontrol->value.integer.value[0] = + mask - ucontrol->value.integer.value[0]; + } + return 0; +} + +static int snd_cs4215_put_single(snd_kcontrol_t * kcontrol, + snd_ctl_elem_value_t * ucontrol) +{ + snd_dbri_t *dbri = snd_kcontrol_chip(kcontrol); + unsigned long flags; + int elem = kcontrol->private_value & 0xff; + int shift = (kcontrol->private_value >> 8) & 0xff; + int mask = (kcontrol->private_value >> 16) & 0xff; + int invert = (kcontrol->private_value >> 24) & 1; + int changed = 0; + unsigned short val; + snd_assert(dbri != NULL, return -EINVAL); + + val = (ucontrol->value.integer.value[0] & mask); + if (invert == 1) + val = mask - val; + val <<= shift; + + if (elem < 4) { + dbri->mm.data[elem] = (dbri->mm.data[elem] & + ~(mask << shift)) | val; + changed = (val != dbri->mm.data[elem]); + } else { + dbri->mm.ctrl[elem - 4] = (dbri->mm.ctrl[elem - 4] & + ~(mask << shift)) | val; + changed = (val != dbri->mm.ctrl[elem - 4]); + } + + dprintk(D_GEN, "put_single: mask=0x%x, changed=%d, " + "mixer-value=%ld, mm-value=0x%x\n", + mask, changed, ucontrol->value.integer.value[0], + dbri->mm.data[elem & 3]); + + if (changed) { + /* First mute outputs, and wait 1/8000 sec (125 us) + * to make sure this takes. This avoids clicking noises. + */ + spin_lock_irqsave(&dbri->lock, flags); + + cs4215_setdata(dbri, 1); + udelay(125); + cs4215_setdata(dbri, 0); + + spin_unlock_irqrestore(&dbri->lock, flags); + } + return changed; +} + +/* Entries 0-3 map to the 4 data timeslots, entries 4-7 map to the 4 control + timeslots. Shift is the bit offset in the timeslot, mask defines the + number of bits. invert is a boolean for use with attenuation. + */ +#define CS4215_SINGLE(xname, entry, shift, mask, invert) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \ + .info = snd_cs4215_info_single, \ + .get = snd_cs4215_get_single, .put = snd_cs4215_put_single, \ + .private_value = entry | (shift << 8) | (mask << 16) | (invert << 24) }, + +static snd_kcontrol_new_t dbri_controls[] __devinitdata = { + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Playback Volume", + .info = snd_cs4215_info_volume, + .get = snd_cs4215_get_volume, + .put = snd_cs4215_put_volume, + .private_value = DBRI_PLAY, + }, + CS4215_SINGLE("Headphone switch", 0, 7, 1, 0) + CS4215_SINGLE("Line out switch", 0, 6, 1, 0) + CS4215_SINGLE("Speaker switch", 1, 6, 1, 0) + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Capture Volume", + .info = snd_cs4215_info_volume, + .get = snd_cs4215_get_volume, + .put = snd_cs4215_put_volume, + .private_value = DBRI_REC, + }, + /* FIXME: mic/line switch */ + CS4215_SINGLE("Line in switch", 2, 4, 1, 0) + CS4215_SINGLE("High Pass Filter switch", 5, 7, 1, 0) + CS4215_SINGLE("Monitor Volume", 3, 4, 0xf, 1) + CS4215_SINGLE("Mic boost", 4, 4, 1, 1) +}; + +#define NUM_CS4215_CONTROLS (sizeof(dbri_controls)/sizeof(snd_kcontrol_new_t)) + +static int __init snd_dbri_mixer(snd_dbri_t * dbri) +{ + snd_card_t *card; + int idx, err; + + snd_assert(dbri != NULL && dbri->card != NULL, return -EINVAL); + + card = dbri->card; + strcpy(card->mixername, card->shortname); + + for (idx = 0; idx < NUM_CS4215_CONTROLS; idx++) { + if ((err = snd_ctl_add(card, + snd_ctl_new1(&dbri_controls[idx], + dbri))) < 0) + return err; + } + + for (idx = DBRI_REC; idx < DBRI_NO_STREAMS; idx++) { + dbri->stream_info[idx].left_gain = 0; + dbri->stream_info[idx].right_gain = 0; + dbri->stream_info[idx].balance = DBRI_MID_BALANCE; + } + + return 0; +} + +/**************************************************************************** + /proc interface +****************************************************************************/ +static void dbri_regs_read(snd_info_entry_t * entry, snd_info_buffer_t * buffer) +{ + snd_dbri_t *dbri = entry->private_data; + + snd_iprintf(buffer, "REG0: 0x%x\n", sbus_readl(dbri->regs + REG0)); + snd_iprintf(buffer, "REG2: 0x%x\n", sbus_readl(dbri->regs + REG2)); + snd_iprintf(buffer, "REG8: 0x%x\n", sbus_readl(dbri->regs + REG8)); + snd_iprintf(buffer, "REG9: 0x%x\n", sbus_readl(dbri->regs + REG9)); +} + +#ifdef DBRI_DEBUG +static void dbri_debug_read(snd_info_entry_t * entry, + snd_info_buffer_t * buffer) +{ + snd_dbri_t *dbri = entry->private_data; + int pipe; + snd_iprintf(buffer, "debug=%d\n", dbri_debug); + + snd_iprintf(buffer, "CHI pipe in=%d, out=%d\n", + dbri->chi_in_pipe, dbri->chi_out_pipe); + for (pipe = 0; pipe < 32; pipe++) { + if (pipe_active(dbri, pipe)) { + struct dbri_pipe *pptr = &dbri->pipes[pipe]; + snd_iprintf(buffer, + "Pipe %d: %s SDP=0x%x desc=%d, " + "len=%d @ %d prev: %d next %d\n", + pipe, + (pptr->direction == + PIPEinput ? "input" : "output"), pptr->sdp, + pptr->desc, pptr->length, pptr->cycle, + pptr->prevpipe, pptr->nextpipe); + } + } +} + +static void dbri_debug_write(snd_info_entry_t * entry, + snd_info_buffer_t * buffer) +{ + char line[80]; + int i; + + if (snd_info_get_line(buffer, line, 80) == 0) { + sscanf(line, "%d\n", &i); + dbri_debug = i & 0x3f; + } +} +#endif + +void snd_dbri_proc(snd_dbri_t * dbri) +{ + snd_info_entry_t *entry; + int err; + + err = snd_card_proc_new(dbri->card, "regs", &entry); + snd_info_set_text_ops(entry, dbri, 1024, dbri_regs_read); + +#ifdef DBRI_DEBUG + err = snd_card_proc_new(dbri->card, "debug", &entry); + snd_info_set_text_ops(entry, dbri, 4096, dbri_debug_read); + entry->mode = S_IFREG | S_IRUGO | S_IWUSR; /* Writable for root */ + entry->c.text.write_size = 256; + entry->c.text.write = dbri_debug_write; +#endif +} + +/* +**************************************************************************** +**************************** Initialization ******************************** +**************************************************************************** +*/ +static void snd_dbri_free(snd_dbri_t * dbri); + +static int __init snd_dbri_create(snd_card_t * card, + struct sbus_dev *sdev, + struct linux_prom_irqs *irq, int dev) +{ + snd_dbri_t *dbri = card->private_data; + int err; + + spin_lock_init(&dbri->lock); + dbri->card = card; + dbri->sdev = sdev; + dbri->irq = irq->pri; + dbri->dbri_version = sdev->prom_name[9]; + + dbri->dma = sbus_alloc_consistent(sdev, sizeof(struct dbri_dma), + &dbri->dma_dvma); + memset((void *)dbri->dma, 0, sizeof(struct dbri_dma)); + + dprintk(D_GEN, "DMA Cmd Block 0x%p (0x%08x)\n", + dbri->dma, dbri->dma_dvma); + + /* Map the registers into memory. */ + dbri->regs_size = sdev->reg_addrs[0].reg_size; + dbri->regs = sbus_ioremap(&sdev->resource[0], 0, + dbri->regs_size, "DBRI Registers"); + if (!dbri->regs) { + printk(KERN_ERR "DBRI: could not allocate registers\n"); + sbus_free_consistent(sdev, sizeof(struct dbri_dma), + (void *)dbri->dma, dbri->dma_dvma); + return -EIO; + } + + err = request_irq(dbri->irq, snd_dbri_interrupt, SA_SHIRQ, + "DBRI audio", dbri); + if (err) { + printk(KERN_ERR "DBRI: Can't get irq %d\n", dbri->irq); + sbus_iounmap(dbri->regs, dbri->regs_size); + sbus_free_consistent(sdev, sizeof(struct dbri_dma), + (void *)dbri->dma, dbri->dma_dvma); + return err; + } + + /* Do low level initialization of the DBRI and CS4215 chips */ + dbri_initialize(dbri); + err = cs4215_init(dbri); + if (err) { + snd_dbri_free(dbri); + return err; + } + + dbri->next = dbri_list; + dbri_list = dbri; + + return 0; +} + +static void snd_dbri_free(snd_dbri_t * dbri) +{ + dprintk(D_GEN, "snd_dbri_free\n"); + dbri_reset(dbri); + + if (dbri->irq) + free_irq(dbri->irq, dbri); + + if (dbri->regs) + sbus_iounmap(dbri->regs, dbri->regs_size); + + if (dbri->dma) + sbus_free_consistent(dbri->sdev, sizeof(struct dbri_dma), + (void *)dbri->dma, dbri->dma_dvma); +} + +static int __init dbri_attach(int prom_node, struct sbus_dev *sdev) +{ + snd_dbri_t *dbri; + struct linux_prom_irqs irq; + struct resource *rp; + snd_card_t *card; + static int dev = 0; + int err; + + if (sdev->prom_name[9] < 'e') { + printk(KERN_ERR "DBRI: unsupported chip version %c found.\n", + sdev->prom_name[9]); + return -EIO; + } + + if (dev >= SNDRV_CARDS) + return -ENODEV; + if (!enable[dev]) { + dev++; + return -ENOENT; + } + + prom_getproperty(prom_node, "intr", (char *)&irq, sizeof(irq)); + + card = snd_card_new(index[dev], id[dev], THIS_MODULE, + sizeof(snd_dbri_t)); + if (card == NULL) + return -ENOMEM; + + strcpy(card->driver, "DBRI"); + strcpy(card->shortname, "Sun DBRI"); + rp = &sdev->resource[0]; + sprintf(card->longname, "%s at 0x%02lx:0x%08lx, irq %s", + card->shortname, + rp->flags & 0xffL, rp->start, __irq_itoa(irq.pri)); + + if ((err = snd_dbri_create(card, sdev, &irq, dev)) < 0) { + snd_card_free(card); + return err; + } + + dbri = (snd_dbri_t *) card->private_data; + if ((err = snd_dbri_pcm(dbri)) < 0) { + snd_dbri_free(dbri); + snd_card_free(card); + return err; + } + + if ((err = snd_dbri_mixer(dbri)) < 0) { + snd_dbri_free(dbri); + snd_card_free(card); + return err; + } + + /* /proc file handling */ + snd_dbri_proc(dbri); + + if ((err = snd_card_register(card)) < 0) { + snd_dbri_free(dbri); + snd_card_free(card); + return err; + } + + printk(KERN_INFO "audio%d at %p (irq %d) is DBRI(%c)+CS4215(%d)\n", + dev, dbri->regs, + dbri->irq, dbri->dbri_version, dbri->mm.version); + dev++; + + return 0; +} + +/* Probe for the dbri chip and then attach the driver. */ +static int __init dbri_init(void) +{ + struct sbus_bus *sbus; + struct sbus_dev *sdev; + int found = 0; + + /* Probe each SBUS for the DBRI chip(s). */ + for_all_sbusdev(sdev, sbus) { + /* + * The version is coded in the last character + */ + if (!strncmp(sdev->prom_name, "SUNW,DBRI", 9)) { + dprintk(D_GEN, "DBRI: Found %s in SBUS slot %d\n", + sdev->prom_name, sdev->slot); + + if (dbri_attach(sdev->prom_node, sdev) == 0) + found++; + } + } + + return (found > 0) ? 0 : -EIO; +} + +static void __exit dbri_exit(void) +{ + snd_dbri_t *this = dbri_list; + + while (this != NULL) { + snd_dbri_t *next = this->next; + snd_card_t *card = this->card; + + snd_dbri_free(this); + snd_card_free(card); + this = next; + } + dbri_list = NULL; +} + +module_init(dbri_init); +module_exit(dbri_exit); -- cgit v1.2.3-55-g7522 From 2c484df0d249323d223f7f58e0f3b992b7414be8 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Thu, 30 Jun 2005 18:54:04 +0200 Subject: [ALSA] Add ARM PXA2xx AC97 driver Documentation,ARM,/arm/Makefile,ARM PXA2XX driver Added ARM PXA2xx AC97 driver by Nicolas Pitre (moved from alsa-driver tree). Signed-off-by: Takashi Iwai --- Documentation/sound/alsa/ALSA-Configuration.txt | 7 + sound/arm/Kconfig | 14 +- sound/arm/Makefile | 8 +- sound/arm/pxa2xx-ac97.c | 410 ++++++++++++++++++++++++ sound/arm/pxa2xx-pcm.c | 367 +++++++++++++++++++++ sound/arm/pxa2xx-pcm.h | 29 ++ 6 files changed, 831 insertions(+), 4 deletions(-) create mode 100644 sound/arm/pxa2xx-ac97.c create mode 100644 sound/arm/pxa2xx-pcm.c create mode 100644 sound/arm/pxa2xx-pcm.h (limited to 'Documentation') diff --git a/Documentation/sound/alsa/ALSA-Configuration.txt b/Documentation/sound/alsa/ALSA-Configuration.txt index 94887dbf1b4b..a18ecb92b356 100644 --- a/Documentation/sound/alsa/ALSA-Configuration.txt +++ b/Documentation/sound/alsa/ALSA-Configuration.txt @@ -1059,6 +1059,13 @@ Prior to version 0.9.0rc4 options had a 'snd_' prefix. This was removed. The power-management is supported. + Module snd-pxa2xx-ac97 (on arm only) + ------------------------------------ + + Module for AC97 driver for the Intel PXA2xx chip + + For ARM architecture only. + Module snd-rme32 ---------------- diff --git a/sound/arm/Kconfig b/sound/arm/Kconfig index 34c1740aa6e9..2e4a5e0d16db 100644 --- a/sound/arm/Kconfig +++ b/sound/arm/Kconfig @@ -20,5 +20,17 @@ config SND_ARMAACI select SND_PCM select SND_AC97_CODEC -endmenu +config SND_PXA2XX_PCM + tristate + select SND_PCM + +config SND_PXA2XX_AC97 + tristate "AC97 driver for the Intel PXA2xx chip" + depends on ARCH_PXA && SND + select SND_PXA2XX_PCM + select SND_AC97_CODEC + help + Say Y or M if you want to support any AC97 codec attached to + the PXA2xx AC97 interface. +endmenu diff --git a/sound/arm/Makefile b/sound/arm/Makefile index f74ec28e1068..103f136926d9 100644 --- a/sound/arm/Makefile +++ b/sound/arm/Makefile @@ -3,9 +3,11 @@ # snd-sa11xx-uda1341-objs := sa11xx-uda1341.o +snd-aaci-objs := aaci.o devdma.o +snd-pxa2xx-pcm-objs := pxa2xx-pcm.o +snd-pxa2xx-ac97-objs := pxa2xx-ac97.o -# Toplevel Module Dependency obj-$(CONFIG_SND_SA11XX_UDA1341) += snd-sa11xx-uda1341.o - obj-$(CONFIG_SND_ARMAACI) += snd-aaci.o -snd-aaci-objs := aaci.o devdma.o +obj-$(CONFIG_SND_PXA2XX_PCM) += snd-pxa2xx-pcm.o +obj-$(CONFIG_SND_PXA2XX_AC97) += snd-pxa2xx-ac97.o diff --git a/sound/arm/pxa2xx-ac97.c b/sound/arm/pxa2xx-ac97.c new file mode 100644 index 000000000000..46052304e230 --- /dev/null +++ b/sound/arm/pxa2xx-ac97.c @@ -0,0 +1,410 @@ +/* + * linux/sound/pxa2xx-ac97.c -- AC97 support for the Intel PXA2xx chip. + * + * Author: Nicolas Pitre + * Created: Dec 02, 2004 + * Copyright: MontaVista Software Inc. + * + * 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 +#include +#include + +#include "pxa2xx-pcm.h" + + +static DECLARE_MUTEX(car_mutex); +static DECLARE_WAIT_QUEUE_HEAD(gsr_wq); +static volatile long gsr_bits; + +static unsigned short pxa2xx_ac97_read(ac97_t *ac97, unsigned short reg) +{ + unsigned short val = -1; + volatile u32 *reg_addr; + + down(&car_mutex); + if (CAR & CAR_CAIP) { + printk(KERN_CRIT"%s: CAR_CAIP already set\n", __FUNCTION__); + goto out; + } + + /* set up primary or secondary codec space */ + reg_addr = (ac97->num & 1) ? &SAC_REG_BASE : &PAC_REG_BASE; + reg_addr += (reg >> 1); + + /* start read access across the ac97 link */ + gsr_bits = 0; + val = *reg_addr; + if (reg == AC97_GPIO_STATUS) + goto out; + wait_event_timeout(gsr_wq, gsr_bits & GSR_SDONE, 1); + if (!gsr_bits & GSR_SDONE) { + printk(KERN_ERR "%s: read error (ac97_reg=%d GSR=%#lx)\n", + __FUNCTION__, reg, gsr_bits); + val = -1; + goto out; + } + + /* valid data now */ + gsr_bits = 0; + val = *reg_addr; + /* but we've just started another cycle... */ + wait_event_timeout(gsr_wq, gsr_bits & GSR_SDONE, 1); + +out: up(&car_mutex); + return val; +} + +static void pxa2xx_ac97_write(ac97_t *ac97, unsigned short reg, unsigned short val) +{ + volatile u32 *reg_addr; + + down(&car_mutex); + + if (CAR & CAR_CAIP) { + printk(KERN_CRIT "%s: CAR_CAIP already set\n", __FUNCTION__); + goto out; + } + + /* set up primary or secondary codec space */ + reg_addr = (ac97->num & 1) ? &SAC_REG_BASE : &PAC_REG_BASE; + reg_addr += (reg >> 1); + gsr_bits = 0; + *reg_addr = val; + wait_event_timeout(gsr_wq, gsr_bits & GSR_CDONE, 1); + if (!gsr_bits & GSR_SDONE) + printk(KERN_ERR "%s: write error (ac97_reg=%d GSR=%#lx)\n", + __FUNCTION__, reg, gsr_bits); + +out: up(&car_mutex); +} + +static void pxa2xx_ac97_reset(ac97_t *ac97) +{ + /* First, try cold reset */ + GCR &= GCR_COLD_RST; /* clear everything but nCRST */ + GCR &= ~GCR_COLD_RST; /* then assert nCRST */ + + gsr_bits = 0; +#ifdef CONFIG_PXA27x + /* PXA27x Developers Manual section 13.5.2.2.1 */ + pxa_set_cken(1 << 31, 1); + udelay(5); + pxa_set_cken(1 << 31, 0); + GCR = GCR_COLD_RST; + udelay(50); +#else + GCR = GCR_COLD_RST; + GCR |= GCR_CDONE_IE|GCR_SDONE_IE; + wait_event_timeout(gsr_wq, gsr_bits & (GSR_PCR | GSR_SCR), 1); +#endif + + if (!((GSR | gsr_bits) & (GSR_PCR | GSR_SCR))) { + printk(KERN_INFO "%s: cold reset timeout (GSR=%#lx)\n", + __FUNCTION__, gsr_bits); + + /* let's try warm reset */ + gsr_bits = 0; +#ifdef CONFIG_PXA27x + /* warm reset broken on Bulverde, + so manually keep AC97 reset high */ + pxa_gpio_mode(113 | GPIO_OUT | GPIO_DFLT_HIGH); + udelay(10); + GCR |= GCR_WARM_RST; + pxa_gpio_mode(113 | GPIO_ALT_FN_2_OUT); + udelay(50); +#else + GCR |= GCR_WARM_RST|GCR_PRIRDY_IEN|GCR_SECRDY_IEN;; + wait_event_timeout(gsr_wq, gsr_bits & (GSR_PCR | GSR_SCR), 1); +#endif + + if (!((GSR | gsr_bits) & (GSR_PCR | GSR_SCR))) + printk(KERN_INFO "%s: warm reset timeout (GSR=%#lx)\n", + __FUNCTION__, gsr_bits); + } + + GCR &= ~(GCR_PRIRDY_IEN|GCR_SECRDY_IEN); + GCR |= GCR_SDONE_IE|GCR_CDONE_IE; +} + +static irqreturn_t pxa2xx_ac97_irq(int irq, void *dev_id, struct pt_regs *regs) +{ + long status; + + status = GSR; + if (status) { + GSR = status; + gsr_bits |= status; + wake_up(&gsr_wq); + +#ifdef CONFIG_PXA27x + /* Although we don't use those we still need to clear them + since they tend to spuriously trigger when MMC is used + (hardware bug? go figure)... */ + MISR = MISR_EOC; + PISR = PISR_EOC; + MCSR = MCSR_EOC; +#endif + + return IRQ_HANDLED; + } + + return IRQ_NONE; +} + +static ac97_bus_ops_t pxa2xx_ac97_ops = { + .read = pxa2xx_ac97_read, + .write = pxa2xx_ac97_write, + .reset = pxa2xx_ac97_reset, +}; + +static pxa2xx_pcm_dma_params_t pxa2xx_ac97_pcm_out = { + .name = "AC97 PCM out", + .dev_addr = __PREG(PCDR), + .drcmr = &DRCMRTXPCDR, + .dcmd = DCMD_INCSRCADDR | DCMD_FLOWTRG | + DCMD_BURST32 | DCMD_WIDTH4, +}; + +static pxa2xx_pcm_dma_params_t pxa2xx_ac97_pcm_in = { + .name = "AC97 PCM in", + .dev_addr = __PREG(PCDR), + .drcmr = &DRCMRRXPCDR, + .dcmd = DCMD_INCTRGADDR | DCMD_FLOWSRC | + DCMD_BURST32 | DCMD_WIDTH4, +}; + +static snd_pcm_t *pxa2xx_ac97_pcm; +static ac97_t *pxa2xx_ac97_ac97; + +static int pxa2xx_ac97_pcm_startup(snd_pcm_substream_t *substream) +{ + snd_pcm_runtime_t *runtime = substream->runtime; + pxa2xx_audio_ops_t *platform_ops; + int r; + + runtime->hw.channels_min = 2; + runtime->hw.channels_max = 2; + + r = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) ? + AC97_RATES_FRONT_DAC : AC97_RATES_ADC; + runtime->hw.rates = pxa2xx_ac97_ac97->rates[r]; + snd_pcm_limit_hw_rates(runtime); + + platform_ops = substream->pcm->card->dev->platform_data; + if (platform_ops && platform_ops->startup) + return platform_ops->startup(substream, platform_ops->priv); + else + return 0; +} + +static void pxa2xx_ac97_pcm_shutdown(snd_pcm_substream_t *substream) +{ + pxa2xx_audio_ops_t *platform_ops; + + platform_ops = substream->pcm->card->dev->platform_data; + if (platform_ops && platform_ops->shutdown) + platform_ops->shutdown(substream, platform_ops->priv); +} + +static int pxa2xx_ac97_pcm_prepare(snd_pcm_substream_t *substream) +{ + snd_pcm_runtime_t *runtime = substream->runtime; + int reg = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) ? + AC97_PCM_FRONT_DAC_RATE : AC97_PCM_LR_ADC_RATE; + return snd_ac97_set_rate(pxa2xx_ac97_ac97, reg, runtime->rate); +} + +static pxa2xx_pcm_client_t pxa2xx_ac97_pcm_client = { + .playback_params = &pxa2xx_ac97_pcm_out, + .capture_params = &pxa2xx_ac97_pcm_in, + .startup = pxa2xx_ac97_pcm_startup, + .shutdown = pxa2xx_ac97_pcm_shutdown, + .prepare = pxa2xx_ac97_pcm_prepare, +}; + +#ifdef CONFIG_PM + +static int pxa2xx_ac97_do_suspend(snd_card_t *card, unsigned int state) +{ + if (card->power_state != SNDRV_CTL_POWER_D3cold) { + pxa2xx_audio_ops_t *platform_ops = card->dev->platform_data; + snd_pcm_suspend_all(pxa2xx_ac97_pcm); + snd_ac97_suspend(pxa2xx_ac97_ac97); + snd_power_change_state(card, SNDRV_CTL_POWER_D3cold); + if (platform_ops && platform_ops->suspend) + platform_ops->suspend(platform_ops->priv); + GCR |= GCR_ACLINK_OFF; + pxa_set_cken(CKEN2_AC97, 0); + } + + return 0; +} + +static int pxa2xx_ac97_do_resume(snd_card_t *card, unsigned int state) +{ + if (card->power_state != SNDRV_CTL_POWER_D0) { + pxa2xx_audio_ops_t *platform_ops = card->dev->platform_data; + pxa_set_cken(CKEN2_AC97, 1); + if (platform_ops && platform_ops->resume) + platform_ops->resume(platform_ops->priv); + snd_ac97_resume(pxa2xx_ac97_ac97); + snd_power_change_state(card, SNDRV_CTL_POWER_D0); + } + + return 0; +} + +static int pxa2xx_ac97_suspend(struct device *_dev, u32 state, u32 level) +{ + snd_card_t *card = dev_get_drvdata(_dev); + int ret = 0; + + if (card && level == SUSPEND_DISABLE) + ret = pxa2xx_ac97_do_suspend(card, SNDRV_CTL_POWER_D3cold); + + return ret; +} + +static int pxa2xx_ac97_resume(struct device *_dev, u32 level) +{ + snd_card_t *card = dev_get_drvdata(_dev); + int ret = 0; + + if (card && level == RESUME_ENABLE) + ret = pxa2xx_ac97_do_resume(card, SNDRV_CTL_POWER_D0); + + return ret; +} + +#else +#define pxa2xx_ac97_suspend NULL +#define pxa2xx_ac97_resume NULL +#endif + +static int pxa2xx_ac97_probe(struct device *dev) +{ + snd_card_t *card; + ac97_bus_t *ac97_bus; + ac97_template_t ac97_template; + int ret; + + ret = -ENOMEM; + card = snd_card_new(SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1, + THIS_MODULE, 0); + if (!card) + goto err; + + card->dev = dev; + strncpy(card->driver, dev->driver->name, sizeof(card->driver)); + + ret = pxa2xx_pcm_new(card, &pxa2xx_ac97_pcm_client, &pxa2xx_ac97_pcm); + if (ret) + goto err; + + ret = request_irq(IRQ_AC97, pxa2xx_ac97_irq, 0, "AC97", NULL); + if (ret < 0) + goto err; + + pxa_gpio_mode(GPIO31_SYNC_AC97_MD); + pxa_gpio_mode(GPIO30_SDATA_OUT_AC97_MD); + pxa_gpio_mode(GPIO28_BITCLK_AC97_MD); + pxa_gpio_mode(GPIO29_SDATA_IN_AC97_MD); +#ifdef CONFIG_PXA27x + /* Use GPIO 113 as AC97 Reset on Bulverde */ + pxa_gpio_mode(113 | GPIO_ALT_FN_2_OUT); +#endif + pxa_set_cken(CKEN2_AC97, 1); + + ret = snd_ac97_bus(card, 0, &pxa2xx_ac97_ops, NULL, &ac97_bus); + if (ret) + goto err; + memset(&ac97_template, 0, sizeof(ac97_template)); + ret = snd_ac97_mixer(ac97_bus, &ac97_template, &pxa2xx_ac97_ac97); + if (ret) + goto err; + + snprintf(card->shortname, sizeof(card->shortname), + "%s", snd_ac97_get_short_name(pxa2xx_ac97_ac97)); + snprintf(card->longname, sizeof(card->longname), + "%s (%s)", dev->driver->name, card->mixername); + + snd_card_set_pm_callback(card, pxa2xx_ac97_do_suspend, + pxa2xx_ac97_do_resume, NULL); + ret = snd_card_register(card); + if (ret == 0) { + dev_set_drvdata(dev, card); + return 0; + } + + err: + if (card) + snd_card_free(card); + if (CKEN & CKEN2_AC97) { + GCR |= GCR_ACLINK_OFF; + free_irq(IRQ_AC97, NULL); + pxa_set_cken(CKEN2_AC97, 0); + } + return ret; +} + +static int pxa2xx_ac97_remove(struct device *dev) +{ + snd_card_t *card = dev_get_drvdata(dev); + + if (card) { + snd_card_free(card); + dev_set_drvdata(dev, NULL); + GCR |= GCR_ACLINK_OFF; + free_irq(IRQ_AC97, NULL); + pxa_set_cken(CKEN2_AC97, 0); + } + + return 0; +} + +static struct device_driver pxa2xx_ac97_driver = { + .name = "pxa2xx-ac97", + .bus = &platform_bus_type, + .probe = pxa2xx_ac97_probe, + .remove = pxa2xx_ac97_remove, + .suspend = pxa2xx_ac97_suspend, + .resume = pxa2xx_ac97_resume, +}; + +static int __init pxa2xx_ac97_init(void) +{ + return driver_register(&pxa2xx_ac97_driver); +} + +static void __exit pxa2xx_ac97_exit(void) +{ + driver_unregister(&pxa2xx_ac97_driver); +} + +module_init(pxa2xx_ac97_init); +module_exit(pxa2xx_ac97_exit); + +MODULE_AUTHOR("Nicolas Pitre"); +MODULE_DESCRIPTION("AC97 driver for the Intel PXA2xx chip"); +MODULE_LICENSE("GPL"); diff --git a/sound/arm/pxa2xx-pcm.c b/sound/arm/pxa2xx-pcm.c new file mode 100644 index 000000000000..b1eb53b02eae --- /dev/null +++ b/sound/arm/pxa2xx-pcm.c @@ -0,0 +1,367 @@ +/* + * linux/sound/arm/pxa2xx-pcm.c -- ALSA PCM interface for the Intel PXA2xx chip + * + * Author: Nicolas Pitre + * Created: Nov 30, 2004 + * Copyright: (C) 2004 MontaVista Software, Inc. + * + * 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 "pxa2xx-pcm.h" + + +static const snd_pcm_hardware_t pxa2xx_pcm_hardware = { + .info = SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_PAUSE, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .period_bytes_min = 32, + .period_bytes_max = 8192 - 32, + .periods_min = 1, + .periods_max = PAGE_SIZE/sizeof(pxa_dma_desc), + .buffer_bytes_max = 128 * 1024, + .fifo_size = 32, +}; + +struct pxa2xx_runtime_data { + int dma_ch; + pxa2xx_pcm_dma_params_t *params; + pxa_dma_desc *dma_desc_array; + dma_addr_t dma_desc_array_phys; +}; + +static int pxa2xx_pcm_hw_params(snd_pcm_substream_t *substream, + snd_pcm_hw_params_t *params) +{ + snd_pcm_runtime_t *runtime = substream->runtime; + struct pxa2xx_runtime_data *rtd = runtime->private_data; + size_t totsize = params_buffer_bytes(params); + size_t period = params_period_bytes(params); + pxa_dma_desc *dma_desc; + dma_addr_t dma_buff_phys, next_desc_phys; + + snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer); + runtime->dma_bytes = totsize; + + dma_desc = rtd->dma_desc_array; + next_desc_phys = rtd->dma_desc_array_phys; + dma_buff_phys = runtime->dma_addr; + do { + next_desc_phys += sizeof(pxa_dma_desc); + dma_desc->ddadr = next_desc_phys; + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + dma_desc->dsadr = dma_buff_phys; + dma_desc->dtadr = rtd->params->dev_addr; + } else { + dma_desc->dsadr = rtd->params->dev_addr; + dma_desc->dtadr = dma_buff_phys; + } + if (period > totsize) + period = totsize; + dma_desc->dcmd = rtd->params->dcmd | period | DCMD_ENDIRQEN; + dma_desc++; + dma_buff_phys += period; + } while (totsize -= period); + dma_desc[-1].ddadr = rtd->dma_desc_array_phys; + + return 0; +} + +static int pxa2xx_pcm_hw_free(snd_pcm_substream_t *substream) +{ + struct pxa2xx_runtime_data *rtd = substream->runtime->private_data; + + *rtd->params->drcmr = 0; + snd_pcm_set_runtime_buffer(substream, NULL); + return 0; +} + +static int pxa2xx_pcm_prepare(snd_pcm_substream_t *substream) +{ + pxa2xx_pcm_client_t *client = substream->private_data; + snd_pcm_runtime_t *runtime = substream->runtime; + struct pxa2xx_runtime_data *rtd = runtime->private_data; + + DCSR(rtd->dma_ch) &= ~DCSR_RUN; + DCSR(rtd->dma_ch) = 0; + DCMD(rtd->dma_ch) = 0; + *rtd->params->drcmr = rtd->dma_ch | DRCMR_MAPVLD; + + return client->prepare(substream); +} + +static int pxa2xx_pcm_trigger(snd_pcm_substream_t *substream, int cmd) +{ + struct pxa2xx_runtime_data *rtd = substream->runtime->private_data; + int ret = 0; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + DDADR(rtd->dma_ch) = rtd->dma_desc_array_phys; + DCSR(rtd->dma_ch) = DCSR_RUN; + break; + + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + DCSR(rtd->dma_ch) &= ~DCSR_RUN; + break; + + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + DCSR(rtd->dma_ch) |= DCSR_RUN; + break; + + default: + ret = -EINVAL; + } + + return ret; +} + +static void pxa2xx_pcm_dma_irq(int dma_ch, void *dev_id, struct pt_regs *regs) +{ + snd_pcm_substream_t *substream = dev_id; + struct pxa2xx_runtime_data *rtd = substream->runtime->private_data; + int dcsr; + + dcsr = DCSR(dma_ch); + DCSR(dma_ch) = dcsr & ~DCSR_STOPIRQEN; + + if (dcsr & DCSR_ENDINTR) { + snd_pcm_period_elapsed(substream); + } else { + printk( KERN_ERR "%s: DMA error on channel %d (DCSR=%#x)\n", + rtd->params->name, dma_ch, dcsr ); + snd_pcm_stop(substream, SNDRV_PCM_STATE_XRUN); + } +} + +static snd_pcm_uframes_t pxa2xx_pcm_pointer(snd_pcm_substream_t *substream) +{ + snd_pcm_runtime_t *runtime = substream->runtime; + struct pxa2xx_runtime_data *rtd = runtime->private_data; + dma_addr_t ptr = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) ? + DSADR(rtd->dma_ch) : DTADR(rtd->dma_ch); + snd_pcm_uframes_t x = bytes_to_frames(runtime, ptr - runtime->dma_addr); + if (x == runtime->buffer_size) + x = 0; + return x; +} + +static int +pxa2xx_pcm_hw_rule_mult32(snd_pcm_hw_params_t *params, snd_pcm_hw_rule_t *rule) +{ + snd_interval_t *i = hw_param_interval(params, rule->var); + int changed = 0; + + if (i->min & 31) { + i->min = (i->min & ~31) + 32; + i->openmin = 0; + changed = 1; + } + + if (i->max & 31) { + i->max &= ~31; + i->openmax = 0; + changed = 1; + } + + return changed; +} + +static int pxa2xx_pcm_open(snd_pcm_substream_t *substream) +{ + pxa2xx_pcm_client_t *client = substream->private_data; + snd_pcm_runtime_t *runtime = substream->runtime; + struct pxa2xx_runtime_data *rtd; + int ret; + + runtime->hw = pxa2xx_pcm_hardware; + + /* + * For mysterious reasons (and despite what the manual says) + * playback samples are lost if the DMA count is not a multiple + * of the DMA burst size. Let's add a rule to enforce that. + */ + ret = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_BYTES, + pxa2xx_pcm_hw_rule_mult32, NULL, + SNDRV_PCM_HW_PARAM_PERIOD_BYTES, -1); + if (ret) + goto out; + ret = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_BUFFER_BYTES, + pxa2xx_pcm_hw_rule_mult32, NULL, + SNDRV_PCM_HW_PARAM_BUFFER_BYTES, -1); + if (ret) + goto out; + + ret = -ENOMEM; + rtd = kmalloc(sizeof(*rtd), GFP_KERNEL); + if (!rtd) + goto out; + rtd->dma_desc_array = + dma_alloc_writecombine(substream->pcm->card->dev, PAGE_SIZE, + &rtd->dma_desc_array_phys, GFP_KERNEL); + if (!rtd->dma_desc_array) + goto err1; + + rtd->params = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) ? + client->playback_params : client->capture_params; + ret = pxa_request_dma(rtd->params->name, DMA_PRIO_LOW, + pxa2xx_pcm_dma_irq, substream); + if (ret < 0) + goto err2; + rtd->dma_ch = ret; + + runtime->private_data = rtd; + ret = client->startup(substream); + if (!ret) + goto out; + + pxa_free_dma(rtd->dma_ch); + err2: + dma_free_writecombine(substream->pcm->card->dev, PAGE_SIZE, + rtd->dma_desc_array, rtd->dma_desc_array_phys); + err1: + kfree(rtd); + out: + return ret; +} + +static int pxa2xx_pcm_close(snd_pcm_substream_t *substream) +{ + pxa2xx_pcm_client_t *client = substream->private_data; + struct pxa2xx_runtime_data *rtd = substream->runtime->private_data; + + pxa_free_dma(rtd->dma_ch); + client->shutdown(substream); + dma_free_writecombine(substream->pcm->card->dev, PAGE_SIZE, + rtd->dma_desc_array, rtd->dma_desc_array_phys); + kfree(rtd); + return 0; +} + +static int +pxa2xx_pcm_mmap(snd_pcm_substream_t *substream, struct vm_area_struct *vma) +{ + snd_pcm_runtime_t *runtime = substream->runtime; + return dma_mmap_writecombine(substream->pcm->card->dev, vma, + runtime->dma_area, + runtime->dma_addr, + runtime->dma_bytes); +} + +static snd_pcm_ops_t pxa2xx_pcm_ops = { + .open = pxa2xx_pcm_open, + .close = pxa2xx_pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = pxa2xx_pcm_hw_params, + .hw_free = pxa2xx_pcm_hw_free, + .prepare = pxa2xx_pcm_prepare, + .trigger = pxa2xx_pcm_trigger, + .pointer = pxa2xx_pcm_pointer, + .mmap = pxa2xx_pcm_mmap, +}; + +static int pxa2xx_pcm_preallocate_dma_buffer(snd_pcm_t *pcm, int stream) +{ + snd_pcm_substream_t *substream = pcm->streams[stream].substream; + struct snd_dma_buffer *buf = &substream->dma_buffer; + size_t size = pxa2xx_pcm_hardware.buffer_bytes_max; + buf->dev.type = SNDRV_DMA_TYPE_DEV; + buf->dev.dev = pcm->card->dev; + buf->private_data = NULL; + buf->area = dma_alloc_writecombine(pcm->card->dev, size, + &buf->addr, GFP_KERNEL); + if (!buf->area) + return -ENOMEM; + buf->bytes = size; + return 0; +} + +static void pxa2xx_pcm_free_dma_buffers(snd_pcm_t *pcm) +{ + snd_pcm_substream_t *substream; + struct snd_dma_buffer *buf; + int stream; + + for (stream = 0; stream < 2; stream++) { + substream = pcm->streams[stream].substream; + if (!substream) + continue; + buf = &substream->dma_buffer; + if (!buf->area) + continue; + dma_free_writecombine(pcm->card->dev, buf->bytes, + buf->area, buf->addr); + buf->area = NULL; + } +} + +static u64 pxa2xx_pcm_dmamask = 0xffffffff; + +int pxa2xx_pcm_new(snd_card_t *card, pxa2xx_pcm_client_t *client, snd_pcm_t **rpcm) +{ + snd_pcm_t *pcm; + int play = client->playback_params ? 1 : 0; + int capt = client->capture_params ? 1 : 0; + int ret; + + ret = snd_pcm_new(card, "PXA2xx-PCM", 0, play, capt, &pcm); + if (ret) + goto out; + + pcm->private_data = client; + pcm->private_free = pxa2xx_pcm_free_dma_buffers; + + if (!card->dev->dma_mask) + card->dev->dma_mask = &pxa2xx_pcm_dmamask; + if (!card->dev->coherent_dma_mask) + card->dev->coherent_dma_mask = 0xffffffff; + + if (play) { + int stream = SNDRV_PCM_STREAM_PLAYBACK; + snd_pcm_set_ops(pcm, stream, &pxa2xx_pcm_ops); + ret = pxa2xx_pcm_preallocate_dma_buffer(pcm, stream); + if (ret) + goto out; + } + if (capt) { + int stream = SNDRV_PCM_STREAM_CAPTURE; + snd_pcm_set_ops(pcm, stream, &pxa2xx_pcm_ops); + ret = pxa2xx_pcm_preallocate_dma_buffer(pcm, stream); + if (ret) + goto out; + } + + if (rpcm) + *rpcm = pcm; + ret = 0; + + out: + return ret; +} + +EXPORT_SYMBOL(pxa2xx_pcm_new); + +MODULE_AUTHOR("Nicolas Pitre"); +MODULE_DESCRIPTION("Intel PXA2xx PCM DMA module"); +MODULE_LICENSE("GPL"); diff --git a/sound/arm/pxa2xx-pcm.h b/sound/arm/pxa2xx-pcm.h new file mode 100644 index 000000000000..43517597cab9 --- /dev/null +++ b/sound/arm/pxa2xx-pcm.h @@ -0,0 +1,29 @@ +/* + * linux/sound/arm/pxa2xx-pcm.h -- ALSA PCM interface for the Intel PXA2xx chip + * + * Author: Nicolas Pitre + * Created: Nov 30, 2004 + * Copyright: MontaVista Software, Inc. + * + * 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. + */ + +typedef struct { + char *name; /* stream identifier */ + u32 dcmd; /* DMA descriptor dcmd field */ + volatile u32 *drcmr; /* the DMA request channel to use */ + u32 dev_addr; /* device physical address for DMA */ +} pxa2xx_pcm_dma_params_t; + +typedef struct { + pxa2xx_pcm_dma_params_t *playback_params; + pxa2xx_pcm_dma_params_t *capture_params; + int (*startup)(snd_pcm_substream_t *); + void (*shutdown)(snd_pcm_substream_t *); + int (*prepare)(snd_pcm_substream_t *); +} pxa2xx_pcm_client_t; + +extern int pxa2xx_pcm_new(snd_card_t *, pxa2xx_pcm_client_t *, snd_pcm_t **); + -- cgit v1.2.3-55-g7522 From 661d04c6f08c16ae63fb3be05d59ee99c6f3de52 Mon Sep 17 00:00:00 2001 From: Dominik Brodowski Date: Thu, 28 Jul 2005 01:07:26 -0700 Subject: [PATCH] pcmcia: update documentation Update the PCMCIA documentation to reflect some more, though older, changes. Parts extracted from an e-mail from Randy Dunlap with his consent. Signed-off-by: Randy Dunlap Signed-off-by: Dominik Brodowski Signed-off-by: Andrew Morton Signed-off-by: Linus Torvalds --- Documentation/pcmcia/driver-changes.txt | 9 +++++++++ 1 file changed, 9 insertions(+) (limited to 'Documentation') diff --git a/Documentation/pcmcia/driver-changes.txt b/Documentation/pcmcia/driver-changes.txt index 59ccc63838c1..403e7b4dcdd4 100644 --- a/Documentation/pcmcia/driver-changes.txt +++ b/Documentation/pcmcia/driver-changes.txt @@ -56,3 +56,12 @@ This file details changes in 2.6 which affect PCMCIA card driver authors: memory regions in-use. The name argument should be a pointer to your driver name. Eg, for pcnet_cs, name should point to the string "pcnet_cs". + +* CardServices is gone + CardServices() in 2.4 is just a big switch statement to call various + services. In 2.6, all of those entry points are exported and called + directly (except for pcmcia_report_error(), just use cs_error() instead). + +* struct pcmcia_driver + You need to use struct pcmcia_driver and pcmcia_{un,}register_driver + instead of {un,}register_pccard_driver -- cgit v1.2.3-55-g7522 From ef4d7cbea773a77b36e732779cab4018ba2c037b Mon Sep 17 00:00:00 2001 From: Andi Kleen Date: Thu, 28 Jul 2005 21:15:34 -0700 Subject: [PATCH] x86_64: Some updates for boot-options.txt Signed-off-by: Andi Kleen Signed-off-by: Andrew Morton Signed-off-by: Linus Torvalds --- Documentation/x86_64/boot-options.txt | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) (limited to 'Documentation') diff --git a/Documentation/x86_64/boot-options.txt b/Documentation/x86_64/boot-options.txt index b9e6be00cadf..476c0c22fbb7 100644 --- a/Documentation/x86_64/boot-options.txt +++ b/Documentation/x86_64/boot-options.txt @@ -47,7 +47,7 @@ Timing notsc Don't use the CPU time stamp counter to read the wall time. This can be used to work around timing problems on multiprocessor systems - with not properly synchronized CPUs. Only useful with a SMP kernel + with not properly synchronized CPUs. report_lost_ticks Report when timer interrupts are lost because some code turned off @@ -74,6 +74,9 @@ Idle loop event. This will make the CPUs eat a lot more power, but may be useful to get slightly better performance in multiprocessor benchmarks. It also makes some profiling using performance counters more accurate. + Please note that on systems with MONITOR/MWAIT support (like Intel EM64T + CPUs) this option has no performance advantage over the normal idle loop. + It may also interact badly with hyperthreading. Rebooting @@ -178,6 +181,5 @@ Debugging Misc noreplacement Don't replace instructions with more appropiate ones - for the CPU. This may be useful on asymmetric MP systems - where some CPU have less capabilities than the others. - + for the CPU. This may be useful on asymmetric MP systems + where some CPU have less capabilities than the others. -- cgit v1.2.3-55-g7522 From 30d07a22a19329c89628a2057b0120245c482c9e Mon Sep 17 00:00:00 2001 From: Daniel Walker Date: Fri, 29 Jul 2005 12:14:07 -0700 Subject: [PATCH] stable_api_nonsense.txt fixes Signed-off-by: Daniel Walker Signed-off-by: Greg Kroah-Hartman Signed-off-by: Linus Torvalds --- Documentation/stable_api_nonsense.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'Documentation') diff --git a/Documentation/stable_api_nonsense.txt b/Documentation/stable_api_nonsense.txt index 3cea13875277..f39c9d714db3 100644 --- a/Documentation/stable_api_nonsense.txt +++ b/Documentation/stable_api_nonsense.txt @@ -132,7 +132,7 @@ to extra work for the USB developers. Since all Linux USB developers do their work on their own time, asking programmers to do extra work for no gain, for free, is not a possibility. -Security issues are also a very important for Linux. When a +Security issues are also very important for Linux. When a security issue is found, it is fixed in a very short amount of time. A number of times this has caused internal kernel interfaces to be reworked to prevent the security problem from occurring. When this -- cgit v1.2.3-55-g7522 From fc185d95ecf3ca62fa9afb5214a69b39060ff537 Mon Sep 17 00:00:00 2001 From: Greg KH Date: Fri, 29 Jul 2005 12:14:34 -0700 Subject: [PATCH] Add the rules about the -stable kernel releases to the Documentation directory This was the last agreed upon set of rules, it's probably time we actually add them to the kernel tree to make them "official". Signed-off-by: Greg Kroah-Hartman Signed-off-by: Linus Torvalds --- Documentation/stable_kernel_rules.txt | 58 +++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 Documentation/stable_kernel_rules.txt (limited to 'Documentation') diff --git a/Documentation/stable_kernel_rules.txt b/Documentation/stable_kernel_rules.txt new file mode 100644 index 000000000000..2c81305090df --- /dev/null +++ b/Documentation/stable_kernel_rules.txt @@ -0,0 +1,58 @@ +Everything you ever wanted to know about Linux 2.6 -stable releases. + +Rules on what kind of patches are accepted, and what ones are not, into +the "-stable" tree: + + - It must be obviously correct and tested. + - It can not bigger than 100 lines, with context. + - It must fix only one thing. + - It must fix a real bug that bothers people (not a, "This could be a + problem..." type thing.) + - It must fix a problem that causes a build error (but not for things + marked CONFIG_BROKEN), an oops, a hang, data corruption, a real + security issue, or some "oh, that's not good" issue. In short, + something critical. + - No "theoretical race condition" issues, unless an explanation of how + the race can be exploited. + - It can not contain any "trivial" fixes in it (spelling changes, + whitespace cleanups, etc.) + - It must be accepted by the relevant subsystem maintainer. + - It must follow Documentation/SubmittingPatches rules. + + +Procedure for submitting patches to the -stable tree: + + - Send the patch, after verifying that it follows the above rules, to + stable@kernel.org. + - The sender will receive an ack when the patch has been accepted into + the queue, or a nak if the patch is rejected. This response might + take a few days, according to the developer's schedules. + - If accepted, the patch will be added to the -stable queue, for review + by other developers. + - Security patches should not be sent to this alias, but instead to the + documented security@kernel.org. + + +Review cycle: + + - When the -stable maintainers decide for a review cycle, the patches + will be sent to the review committee, and the maintainer of the + affected area of the patch (unless the submitter is the maintainer of + the area) and CC: to the linux-kernel mailing list. + - The review committee has 48 hours in which to ack or nak the patch. + - If the patch is rejected by a member of the committee, or linux-kernel + members object to the patch, bringing up issues that the maintainers + and members did not realize, the patch will be dropped from the + queue. + - At the end of the review cycle, the acked patches will be added to + the latest -stable release, and a new -stable release will happen. + - Security patches will be accepted into the -stable tree directly from + the security kernel team, and not go through the normal review cycle. + Contact the kernel security team for more details on this procedure. + + +Review committe: + + - This will be made up of a number of kernel developers who have + volunteered for this task, and a few that haven't. + -- cgit v1.2.3-55-g7522