From c002f42543e155dd2b5b5039ea2637ab26c82513 Mon Sep 17 00:00:00 2001 From: Anton Altaparmakov Date: Thu, 3 Feb 2005 12:02:56 +0000 Subject: NTFS: - Add disable_sparse mount option together with a per volume sparse enable bit which is set appropriately and a per inode sparse disable bit which is preset on some system file inodes as appropriate. - Enforce that sparse support is disabled on NTFS volumes pre 3.0. Signed-off-by: Anton Altaparmakov --- Documentation/filesystems/ntfs.txt | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) (limited to 'Documentation') diff --git a/Documentation/filesystems/ntfs.txt b/Documentation/filesystems/ntfs.txt index f89b440fad1..cb3cb8c06e9 100644 --- a/Documentation/filesystems/ntfs.txt +++ b/Documentation/filesystems/ntfs.txt @@ -21,7 +21,7 @@ Overview ======== Linux-NTFS comes with a number of user-space programs known as ntfsprogs. -These include mkntfs, a full-featured ntfs file system format utility, +These include mkntfs, a full-featured ntfs filesystem format utility, ntfsundelete used for recovering files that were unintentionally deleted from an NTFS volume and ntfsresize which is used to resize an NTFS partition. See the web site for more information. @@ -149,7 +149,14 @@ case_sensitive= If case_sensitive is specified, treat all file names as name, if it exists. If case_sensitive, you will need to provide the correct case of the short file name. -errors=opt What to do when critical file system errors are found. +disable_sparse= If disable_sparse is specified, creation of sparse + regions, i.e. holes, inside files is disabled for the + volume (for the duration of this mount only). By + default, creation of sparse regions is enabled, which + is consistent with the behaviour of traditional Unix + filesystems. + +errors=opt What to do when critical filesystem errors are found. Following values can be used for "opt": continue: DEFAULT, try to clean-up as much as possible, e.g. marking a corrupt inode as -- cgit v1.2.3 From af859a42d798f047fbfe198ed315a942662c39d2 Mon Sep 17 00:00:00 2001 From: Anton Altaparmakov Date: Sat, 25 Jun 2005 21:07:27 +0100 Subject: NTFS: Prepare for 2.1.23 release: Update documentation and bump version. Signed-off-by: Anton Altaparmakov --- Documentation/filesystems/ntfs.txt | 15 +++++++++++++++ fs/ntfs/ChangeLog | 25 ++++++------------------- fs/ntfs/Makefile | 2 +- fs/ntfs/attrib.c | 2 +- 4 files changed, 23 insertions(+), 21 deletions(-) (limited to 'Documentation') diff --git a/Documentation/filesystems/ntfs.txt b/Documentation/filesystems/ntfs.txt index cb3cb8c06e9..1415b96ed49 100644 --- a/Documentation/filesystems/ntfs.txt +++ b/Documentation/filesystems/ntfs.txt @@ -439,6 +439,21 @@ ChangeLog Note, a technical ChangeLog aimed at kernel hackers is in fs/ntfs/ChangeLog. +2.1.23: + - Stamp the user space journal, aka transaction log, aka $UsnJrnl, if + it is present and active thus telling Windows and applications using + the transaction log that changes can have happened on the volume + which are not recorded in $UsnJrnl. + - Detect the case when Windows has been hibernated (suspended to disk) + and if this is the case do not allow (re)mounting read-write to + prevent data corruption when you boot back into the suspended + Windows session. + - Implement extension of resident files using the normal file write + code paths, i.e. most very small files can be extended to be a little + bit bigger but not by much. + - Improve handling of ntfs volumes with errors and strange boot sectors + in particular. + - Fix various bugs. 2.1.22: - Improve handling of ntfs volumes with errors. - Fix various bugs and race conditions. diff --git a/fs/ntfs/ChangeLog b/fs/ntfs/ChangeLog index a6d2b943a14..3d2cac4061d 100644 --- a/fs/ntfs/ChangeLog +++ b/fs/ntfs/ChangeLog @@ -22,35 +22,22 @@ ToDo/Notes: - Enable the code for setting the NT4 compatibility flag when we start making NTFS 1.2 specific modifications. -2.1.23-WIP +2.1.23 - Implement extension of resident files and make writing safe as well as + many bug fixes, cleanups, and enhancements... - Add printk rate limiting for ntfs_warning() and ntfs_error() when compiled without debug. This avoids a possible denial of service attack. Thanks to Carl-Daniel Hailfinger from SuSE for pointing this out. - Fix compilation warnings on ia64. (Randy Dunlap) - - Use i_size_read() in fs/ntfs/attrib.c::ntfs_attr_set(). - - Use i_size_read() in fs/ntfs/logfile.c::ntfs_{check,empty}_logfile(). - - Use i_size_read() once and then use the cached value in - fs/ntfs/lcnalloc.c::ntfs_cluster_alloc(). - - Use i_size_read() in fs/ntfs/file.c::ntfs_file_open(). + - Use i_size_{read,write}() instead of reading i_size by hand and cache + the value where apropriate. - Add size_lock to the ntfs_inode structure. This is an rw spinlock and it locks against access to the inode sizes. Note, ->size_lock is also accessed from irq context so you must use the _irqsave and - _irqrestore lock and unlock functions, respectively. - - Use i_size_read() in fs/ntfs/compress.c at the start of the read and - use the cached value afterwards. Cache the initialized_size in the - same way and protect access to the two sizes using the size_lock. - - Use i_size_read() in fs/ntfs/dir.c once and then use the cached - value afterwards. - - Use i_size_read() in fs/ntfs/super.c once and then use the cached - value afterwards. Cache the initialized_size in the same way and - protect access to the two sizes using the size_lock. + _irqrestore lock and unlock functions, respectively. Protect all + accesses to allocated_size, initialized_size, and compressed_size. - Minor optimization to fs/ntfs/super.c::ntfs_statfs() and its helpers. - - Use i_size_read() in fs/ntfs/inode.c once and then use the cached - value afterwards when reading the size of the bitmap inode. - - Use i_size_{read,write}() in fs/ntfs/{aops.c,mft.c} and protect - access to the i_size and other size fields using the size_lock. - Implement extension of resident files in the regular file write code paths (fs/ntfs/aops.c::ntfs_{prepare,commit}_write()). At present this only works until the data attribute becomes too big for the mft diff --git a/fs/ntfs/Makefile b/fs/ntfs/Makefile index 59f9606a82a..f083f27d8b6 100644 --- a/fs/ntfs/Makefile +++ b/fs/ntfs/Makefile @@ -6,7 +6,7 @@ ntfs-objs := aops.o attrib.o collate.o compress.o debug.o dir.o file.o \ index.o inode.o mft.o mst.o namei.o runlist.o super.o sysctl.o \ unistr.o upcase.o -EXTRA_CFLAGS = -DNTFS_VERSION=\"2.1.23-WIP\" +EXTRA_CFLAGS = -DNTFS_VERSION=\"2.1.23\" ifeq ($(CONFIG_NTFS_DEBUG),y) EXTRA_CFLAGS += -DDEBUG diff --git a/fs/ntfs/attrib.c b/fs/ntfs/attrib.c index 543d47fa5fc..cd0f9e740b1 100644 --- a/fs/ntfs/attrib.c +++ b/fs/ntfs/attrib.c @@ -1324,7 +1324,7 @@ int ntfs_attr_make_non_resident(ntfs_inode *ni) if (IS_ERR(rl)) { err = PTR_ERR(rl); ntfs_debug("Failed to allocate cluster%s, error code " - "%i.\n", (new_size >> + "%i.", (new_size >> vol->cluster_size_bits) > 1 ? "s" : "", err); goto page_err_out; -- cgit v1.2.3 From 12413197eef2a29e0b9fb0fa541f5cbaeb1d3f3f Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Sat, 11 Jun 2005 01:05:01 +0200 Subject: [SCSI] remove scsi_set_device scsi_add_host is the proper place to set the device, but people copy the scsi_set_device usage from older drivers again and again. note that this leaves some legacy drivers like qlogicisp/qlogicfc without pci association in sysfs, but they're scheduled to go away soon anyway. Signed-off-by: James Bottomley --- Documentation/scsi/scsi_mid_low_api.txt | 15 --------------- drivers/message/fusion/mptfc.c | 4 ---- drivers/message/fusion/mptspi.c | 4 ---- drivers/scsi/advansys.c | 2 -- drivers/scsi/aic7xxx_old.c | 1 - drivers/scsi/cpqfcTSinit.c | 1 - drivers/scsi/fdomain.c | 1 - drivers/scsi/gdth.c | 4 +--- drivers/scsi/hosts.c | 5 ----- drivers/scsi/ips.h | 2 +- drivers/scsi/libata-core.c | 2 +- drivers/scsi/megaraid/megaraid_mbox.c | 1 - drivers/scsi/ncr53c8xx.c | 1 - drivers/scsi/nsp32.c | 4 +--- drivers/scsi/qlogicfc.c | 1 - drivers/scsi/qlogicisp.c | 1 - include/scsi/scsi_host.h | 6 ------ 17 files changed, 4 insertions(+), 51 deletions(-) (limited to 'Documentation') diff --git a/Documentation/scsi/scsi_mid_low_api.txt b/Documentation/scsi/scsi_mid_low_api.txt index da176c95d0f..7536823c0cb 100644 --- a/Documentation/scsi/scsi_mid_low_api.txt +++ b/Documentation/scsi/scsi_mid_low_api.txt @@ -388,7 +388,6 @@ Summary: scsi_remove_device - detach and remove a SCSI device scsi_remove_host - detach and remove all SCSI devices owned by host scsi_report_bus_reset - report scsi _bus_ reset observed - scsi_set_device - place device reference in host structure scsi_track_queue_full - track successive QUEUE_FULL events scsi_unblock_requests - allow further commands to be queued to given host scsi_unregister - [calls scsi_host_put()] @@ -740,20 +739,6 @@ int scsi_remove_host(struct Scsi_Host *shost) void scsi_report_bus_reset(struct Scsi_Host * shost, int channel) -/** - * scsi_set_device - place device reference in host structure - * @shost: a pointer to a scsi host instance - * @pdev: pointer to device instance to assign - * - * Returns nothing - * - * Might block: no - * - * Defined in: include/scsi/scsi_host.h . - **/ -void scsi_set_device(struct Scsi_Host * shost, struct device * dev) - - /** * scsi_track_queue_full - track successive QUEUE_FULL events on given * device to determine if and when there is a need diff --git a/drivers/message/fusion/mptfc.c b/drivers/message/fusion/mptfc.c index d8d65397e06..79959562248 100644 --- a/drivers/message/fusion/mptfc.c +++ b/drivers/message/fusion/mptfc.c @@ -267,10 +267,6 @@ mptfc_probe(struct pci_dev *pdev, const struct pci_device_id *id) sh->sg_tablesize = numSGE; } - /* Set the pci device pointer in Scsi_Host structure. - */ - scsi_set_device(sh, &ioc->pcidev->dev); - spin_unlock_irqrestore(&ioc->FreeQlock, flags); hd = (MPT_SCSI_HOST *) sh->hostdata; diff --git a/drivers/message/fusion/mptspi.c b/drivers/message/fusion/mptspi.c index 5f9a61b85b3..d2b53eac9c9 100644 --- a/drivers/message/fusion/mptspi.c +++ b/drivers/message/fusion/mptspi.c @@ -287,10 +287,6 @@ mptspi_probe(struct pci_dev *pdev, const struct pci_device_id *id) sh->sg_tablesize = numSGE; } - /* Set the pci device pointer in Scsi_Host structure. - */ - scsi_set_device(sh, &ioc->pcidev->dev); - spin_unlock_irqrestore(&ioc->FreeQlock, flags); hd = (MPT_SCSI_HOST *) sh->hostdata; diff --git a/drivers/scsi/advansys.c b/drivers/scsi/advansys.c index 04cb5c405a2..a53d43352d9 100644 --- a/drivers/scsi/advansys.c +++ b/drivers/scsi/advansys.c @@ -4556,8 +4556,6 @@ advansys_detect(struct scsi_host_template *tpnt) continue; } - scsi_set_device(shp, dev); - /* Save a pointer to the Scsi_Host of each board found. */ asc_host[asc_board_count++] = shp; diff --git a/drivers/scsi/aic7xxx_old.c b/drivers/scsi/aic7xxx_old.c index fac091e7093..52b72d7794f 100644 --- a/drivers/scsi/aic7xxx_old.c +++ b/drivers/scsi/aic7xxx_old.c @@ -8448,7 +8448,6 @@ aic7xxx_alloc(Scsi_Host_Template *sht, struct aic7xxx_host *temp) } p->host_no = host->host_no; } - scsi_set_device(host, &p->pdev->dev); return (p); } diff --git a/drivers/scsi/cpqfcTSinit.c b/drivers/scsi/cpqfcTSinit.c index 5674ada6d5c..d72be0ce89c 100644 --- a/drivers/scsi/cpqfcTSinit.c +++ b/drivers/scsi/cpqfcTSinit.c @@ -336,7 +336,6 @@ int cpqfcTS_detect(Scsi_Host_Template *ScsiHostTemplate) DEBUG_PCI(printk(" PciDev->baseaddress[3]= %lx\n", PciDev->resource[3].start)); - scsi_set_device(HostAdapter, &PciDev->dev); HostAdapter->irq = PciDev->irq; // copy for Scsi layers // HP Tachlite uses two (255-byte) ranges of Port I/O (lower & upper), diff --git a/drivers/scsi/fdomain.c b/drivers/scsi/fdomain.c index 4ba6a15cf43..aecf32dd0bd 100644 --- a/drivers/scsi/fdomain.c +++ b/drivers/scsi/fdomain.c @@ -938,7 +938,6 @@ struct Scsi_Host *__fdomain_16x0_detect(struct scsi_host_template *tpnt ) } shpnt->irq = interrupt_level; shpnt->io_port = port_base; - scsi_set_device(shpnt, &pdev->dev); shpnt->n_io_port = 0x10; print_banner( shpnt ); diff --git a/drivers/scsi/gdth.c b/drivers/scsi/gdth.c index 4552cccd283..af682301bea 100644 --- a/drivers/scsi/gdth.c +++ b/drivers/scsi/gdth.c @@ -4521,9 +4521,7 @@ static int __init gdth_detect(Scsi_Host_Template *shtp) ha->virt_bus = hdr_channel; -#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,0) - scsi_set_device(shp, &pcistr[ctr].pdev->dev); -#else +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0) scsi_set_pci_device(shp, pcistr[ctr].pdev); #endif if (!(ha->cache_feat & ha->raw_feat & ha->screen_feat &GDT_64BIT)|| diff --git a/drivers/scsi/hosts.c b/drivers/scsi/hosts.c index d7a38b6713f..5feb886c339 100644 --- a/drivers/scsi/hosts.c +++ b/drivers/scsi/hosts.c @@ -180,11 +180,6 @@ static void scsi_host_dev_release(struct device *dev) scsi_destroy_command_freelist(shost); kfree(shost->shost_data); - /* - * Some drivers (eg aha1542) do scsi_register()/scsi_unregister() - * during probing without performing a scsi_set_device() in between. - * In this case dev->parent is NULL. - */ if (parent) put_device(parent); kfree(shost); diff --git a/drivers/scsi/ips.h b/drivers/scsi/ips.h index 906a76158fa..480e06f4d6a 100644 --- a/drivers/scsi/ips.h +++ b/drivers/scsi/ips.h @@ -111,7 +111,7 @@ #define IPS_UNREGISTER_HOSTS(SHT) #define IPS_ADD_HOST(shost,device) do { scsi_add_host(shost,device); scsi_scan_host(shost); } while (0) #define IPS_REMOVE_HOST(shost) scsi_remove_host(shost) - #define IPS_SCSI_SET_DEVICE(sh,ha) scsi_set_device(sh, &(ha)->pcidev->dev) + #define IPS_SCSI_SET_DEVICE(sh,ha) do { } while (0) #define IPS_PRINTK(level, pcidev, format, arg...) \ dev_printk(level , &((pcidev)->dev) , format , ## arg) #endif diff --git a/drivers/scsi/libata-core.c b/drivers/scsi/libata-core.c index 36b401fee1f..a974dc8984b 100644 --- a/drivers/scsi/libata-core.c +++ b/drivers/scsi/libata-core.c @@ -3748,7 +3748,7 @@ static void ata_host_init(struct ata_port *ap, struct Scsi_Host *host, host->max_channel = 1; host->unique_id = ata_unique_id++; host->max_cmd_len = 12; - scsi_set_device(host, ent->dev); + scsi_assign_lock(host, &host_set->lock); ap->flags = ATA_FLAG_PORT_DISABLED; diff --git a/drivers/scsi/megaraid/megaraid_mbox.c b/drivers/scsi/megaraid/megaraid_mbox.c index cbe43024627..d47be8e0ea3 100644 --- a/drivers/scsi/megaraid/megaraid_mbox.c +++ b/drivers/scsi/megaraid/megaraid_mbox.c @@ -719,7 +719,6 @@ megaraid_io_attach(adapter_t *adapter) // export the parameters required by the mid-layer scsi_assign_lock(host, adapter->host_lock); - scsi_set_device(host, &adapter->pdev->dev); host->irq = adapter->irq; host->unique_id = adapter->unique_id; diff --git a/drivers/scsi/ncr53c8xx.c b/drivers/scsi/ncr53c8xx.c index 2a0e42ec27d..519486d24b2 100644 --- a/drivers/scsi/ncr53c8xx.c +++ b/drivers/scsi/ncr53c8xx.c @@ -7756,7 +7756,6 @@ struct Scsi_Host * __init ncr_attach(struct scsi_host_template *tpnt, * your module_init */ BUG_ON(!ncr53c8xx_transport_template); instance->transportt = ncr53c8xx_transport_template; - scsi_set_device(instance, device->dev); /* Patch script to physical addresses */ ncr_script_fill(&script0, &scripth0); diff --git a/drivers/scsi/nsp32.c b/drivers/scsi/nsp32.c index 5159ceea319..6367f009cd7 100644 --- a/drivers/scsi/nsp32.c +++ b/drivers/scsi/nsp32.c @@ -2719,9 +2719,7 @@ static int nsp32_detect(Scsi_Host_Template *sht) host->unique_id = data->BaseAddress; host->n_io_port = data->NumAddress; host->base = (unsigned long)data->MmioAddress; -#if (LINUX_VERSION_CODE > KERNEL_VERSION(2,5,63)) - scsi_set_device(host, &PCIDEV->dev); -#else +#if (LINUX_VERSION_CODE <= KERNEL_VERSION(2,5,63)) scsi_set_pci_device(host, PCIDEV); #endif diff --git a/drivers/scsi/qlogicfc.c b/drivers/scsi/qlogicfc.c index ddf0f4277ee..a4b3b3fd481 100644 --- a/drivers/scsi/qlogicfc.c +++ b/drivers/scsi/qlogicfc.c @@ -746,7 +746,6 @@ static int isp2x00_detect(Scsi_Host_Template * tmpt) printk("qlogicfc%d : could not register host.\n", hosts); continue; } - scsi_set_device(host, &pdev->dev); host->max_id = QLOGICFC_MAX_ID + 1; host->max_lun = QLOGICFC_MAX_LUN; hostdata = (struct isp2x00_hostdata *) host->hostdata; diff --git a/drivers/scsi/qlogicisp.c b/drivers/scsi/qlogicisp.c index 6d29e1b864e..6c9266b8ffd 100644 --- a/drivers/scsi/qlogicisp.c +++ b/drivers/scsi/qlogicisp.c @@ -694,7 +694,6 @@ static int isp1020_detect(Scsi_Host_Template *tmpt) memset(hostdata, 0, sizeof(struct isp1020_hostdata)); hostdata->pci_dev = pdev; - scsi_set_device(host, &pdev->dev); if (isp1020_init(host)) goto fail_and_unregister; diff --git a/include/scsi/scsi_host.h b/include/scsi/scsi_host.h index db9914adeac..81d5234f677 100644 --- a/include/scsi/scsi_host.h +++ b/include/scsi/scsi_host.h @@ -641,12 +641,6 @@ static inline void scsi_assign_lock(struct Scsi_Host *shost, spinlock_t *lock) shost->host_lock = lock; } -static inline void scsi_set_device(struct Scsi_Host *shost, - struct device *dev) -{ - shost->shost_gendev.parent = dev; -} - static inline struct device *scsi_get_device(struct Scsi_Host *shost) { return shost->shost_gendev.parent; -- cgit v1.2.3 From ba6d2377c85c9b8a793f455d8c9b6cf31985d70f Mon Sep 17 00:00:00 2001 From: Anton Altaparmakov Date: Sun, 26 Jun 2005 22:12:02 +0100 Subject: NTFS: Fix a nasty deadlock that appeared in recent kernels. The situation: VFS inode X on a mounted ntfs volume is dirty. For same inode X, the ntfs_inode is dirty and thus corresponding on-disk inode, i.e. mft record, which is in a dirty PAGE_CACHE_PAGE belonging to the table of inodes, i.e. $MFT, inode 0. What happens: Process 1: sys_sync()/umount()/whatever... calls __sync_single_inode() for $MFT -> do_writepages() -> write_page for the dirty page containing the on-disk inode X, the page is now locked -> ntfs_write_mst_block() which clears PageUptodate() on the page to prevent anyone else getting hold of it whilst it does the write out. This is necessary as the on-disk inode needs "fixups" applied before the write to disk which are removed again after the write and PageUptodate is then set again. It then analyses the page looking for dirty on-disk inodes and when it finds one it calls ntfs_may_write_mft_record() to see if it is safe to write this on-disk inode. This then calls ilookup5() to check if the corresponding VFS inode is in icache(). This in turn calls ifind() which waits on the inode lock via wait_on_inode whilst holding the global inode_lock. Process 2: pdflush results in a call to __sync_single_inode for the same VFS inode X on the ntfs volume. This locks the inode (I_LOCK) then calls write-inode -> ntfs_write_inode -> map_mft_record() -> read_cache_page() for the page (in page cache of table of inodes $MFT, inode 0) containing the on-disk inode. This page has PageUptodate() clear because of Process 1 (see above) so read_cache_page() blocks when it tries to take the page lock for the page so it can call ntfs_read_page(). Thus Process 1 is holding the page lock on the page containing the on-disk inode X and it is waiting on the inode X to be unlocked in ifind() so it can write the page out and then unlock the page. And Process 2 is holding the inode lock on inode X and is waiting for the page to be unlocked so it can call ntfs_readpage() or discover that Process 1 set PageUptodate() again and use the page. Thus we have a deadlock due to ifind() waiting on the inode lock. The solution: The fix is to use the newly introduced ilookup5_nowait() which does not wait on the inode's lock and hence avoids the deadlock. This is safe as we do not care about the VFS inode and only use the fact that it is in the VFS inode cache and the fact that the vfs and ntfs inodes are one struct in memory to find the ntfs inode in memory if present. Also, the ntfs inode has its own locking so it does not matter if the vfs inode is locked. Signed-off-by: Anton Altaparmakov --- Documentation/filesystems/ntfs.txt | 5 ++++- fs/ntfs/ChangeLog | 42 ++++++++++++++++++++++++++++++++++++++ fs/ntfs/mft.c | 29 +++++++++++++++++--------- 3 files changed, 65 insertions(+), 11 deletions(-) (limited to 'Documentation') diff --git a/Documentation/filesystems/ntfs.txt b/Documentation/filesystems/ntfs.txt index 1415b96ed49..eef4aca0c75 100644 --- a/Documentation/filesystems/ntfs.txt +++ b/Documentation/filesystems/ntfs.txt @@ -451,9 +451,12 @@ Note, a technical ChangeLog aimed at kernel hackers is in fs/ntfs/ChangeLog. - Implement extension of resident files using the normal file write code paths, i.e. most very small files can be extended to be a little bit bigger but not by much. + - Add new mount option "disable_sparse". (See list of mount options + above for details.) - Improve handling of ntfs volumes with errors and strange boot sectors in particular. - - Fix various bugs. + - Fix various bugs including a nasty deadlock that appeared in recent + kernels (around 2.6.11-2.6.12 timeframe). 2.1.22: - Improve handling of ntfs volumes with errors. - Fix various bugs and race conditions. diff --git a/fs/ntfs/ChangeLog b/fs/ntfs/ChangeLog index 3d2cac4061d..9709fac6531 100644 --- a/fs/ntfs/ChangeLog +++ b/fs/ntfs/ChangeLog @@ -132,6 +132,48 @@ ToDo/Notes: the already mapped runlist fragment which causes ntfs_mapping_pairs_decompress() to fail and return error. Update ntfs_attr_find_vcn_nolock() accordingly. + - Fix a nasty deadlock that appeared in recent kernels. + The situation: VFS inode X on a mounted ntfs volume is dirty. For + same inode X, the ntfs_inode is dirty and thus corresponding on-disk + inode, i.e. mft record, which is in a dirty PAGE_CACHE_PAGE belonging + to the table of inodes, i.e. $MFT, inode 0. + What happens: + Process 1: sys_sync()/umount()/whatever... calls + __sync_single_inode() for $MFT -> do_writepages() -> write_page for + the dirty page containing the on-disk inode X, the page is now locked + -> ntfs_write_mst_block() which clears PageUptodate() on the page to + prevent anyone else getting hold of it whilst it does the write out. + This is necessary as the on-disk inode needs "fixups" applied before + the write to disk which are removed again after the write and + PageUptodate is then set again. It then analyses the page looking + for dirty on-disk inodes and when it finds one it calls + ntfs_may_write_mft_record() to see if it is safe to write this + on-disk inode. This then calls ilookup5() to check if the + corresponding VFS inode is in icache(). This in turn calls ifind() + which waits on the inode lock via wait_on_inode whilst holding the + global inode_lock. + Process 2: pdflush results in a call to __sync_single_inode for the + same VFS inode X on the ntfs volume. This locks the inode (I_LOCK) + then calls write-inode -> ntfs_write_inode -> map_mft_record() -> + read_cache_page() for the page (in page cache of table of inodes + $MFT, inode 0) containing the on-disk inode. This page has + PageUptodate() clear because of Process 1 (see above) so + read_cache_page() blocks when it tries to take the page lock for the + page so it can call ntfs_read_page(). + Thus Process 1 is holding the page lock on the page containing the + on-disk inode X and it is waiting on the inode X to be unlocked in + ifind() so it can write the page out and then unlock the page. + And Process 2 is holding the inode lock on inode X and is waiting for + the page to be unlocked so it can call ntfs_readpage() or discover + that Process 1 set PageUptodate() again and use the page. + Thus we have a deadlock due to ifind() waiting on the inode lock. + The solution: The fix is to use the newly introduced + ilookup5_nowait() which does not wait on the inode's lock and hence + avoids the deadlock. This is safe as we do not care about the VFS + inode and only use the fact that it is in the VFS inode cache and the + fact that the vfs and ntfs inodes are one struct in memory to find + the ntfs inode in memory if present. Also, the ntfs inode has its + own locking so it does not matter if the vfs inode is locked. 2.1.22 - Many bug and race fixes and error handling improvements. diff --git a/fs/ntfs/mft.c b/fs/ntfs/mft.c index 3d0ba8e60ad..ac9ff39aa83 100644 --- a/fs/ntfs/mft.c +++ b/fs/ntfs/mft.c @@ -948,20 +948,23 @@ BOOL ntfs_may_write_mft_record(ntfs_volume *vol, const unsigned long mft_no, na.name_len = 0; na.type = AT_UNUSED; /* - * For inode 0, i.e. $MFT itself, we cannot use ilookup5() from here or - * we deadlock because the inode is already locked by the kernel - * (fs/fs-writeback.c::__sync_single_inode()) and ilookup5() waits - * until the inode is unlocked before returning it and it never gets - * unlocked because ntfs_should_write_mft_record() never returns. )-: - * Fortunately, we have inode 0 pinned in icache for the duration of - * the mount so we can access it directly. + * Optimize inode 0, i.e. $MFT itself, since we have it in memory and + * we get here for it rather often. */ if (!mft_no) { /* Balance the below iput(). */ vi = igrab(mft_vi); BUG_ON(vi != mft_vi); - } else - vi = ilookup5(sb, mft_no, (test_t)ntfs_test_inode, &na); + } else { + /* + * Have to use ilookup5_nowait() since ilookup5() waits for the + * inode lock which causes ntfs to deadlock when a concurrent + * inode write via the inode dirty code paths and the page + * dirty code path of the inode dirty code path when writing + * $MFT occurs. + */ + vi = ilookup5_nowait(sb, mft_no, (test_t)ntfs_test_inode, &na); + } if (vi) { ntfs_debug("Base inode 0x%lx is in icache.", mft_no); /* The inode is in icache. */ @@ -1016,7 +1019,13 @@ BOOL ntfs_may_write_mft_record(ntfs_volume *vol, const unsigned long mft_no, na.mft_no = MREF_LE(m->base_mft_record); ntfs_debug("Mft record 0x%lx is an extent record. Looking for base " "inode 0x%lx in icache.", mft_no, na.mft_no); - vi = ilookup5(sb, na.mft_no, (test_t)ntfs_test_inode, &na); + if (!na.mft_no) { + /* Balance the below iput(). */ + vi = igrab(mft_vi); + BUG_ON(vi != mft_vi); + } else + vi = ilookup5_nowait(sb, na.mft_no, (test_t)ntfs_test_inode, + &na); if (!vi) { /* * The base inode is not in icache, write this extent mft -- cgit v1.2.3 From 6f97933d0fd13920d7d53b6e0107bb674b3a1f0b Mon Sep 17 00:00:00 2001 From: Robert Love Date: Fri, 15 Jul 2005 03:56:33 -0700 Subject: [PATCH] inotify: documentation update Clean up and expand some of the inotify documentation. Signed-off-by: Robert Love Signed-off-by: Andrew Morton Signed-off-by: Linus Torvalds --- Documentation/filesystems/inotify.txt | 77 ++++++++++++++++++++--------------- 1 file changed, 45 insertions(+), 32 deletions(-) (limited to 'Documentation') diff --git a/Documentation/filesystems/inotify.txt b/Documentation/filesystems/inotify.txt index 2c716041f57..6d501903f68 100644 --- a/Documentation/filesystems/inotify.txt +++ b/Documentation/filesystems/inotify.txt @@ -1,18 +1,22 @@ - inotify - a powerful yet simple file change notification system + inotify + a powerful yet simple file change notification system Document started 15 Mar 2005 by Robert Love + (i) User Interface -Inotify is controlled by a set of three sys calls +Inotify is controlled by a set of three system calls and normal file I/O on a +returned file descriptor. -First step in using inotify is to initialise an inotify instance +First step in using inotify is to initialise an inotify instance: int fd = inotify_init (); +Each instance is associated with a unique, ordered queue. + Change events are managed by "watches". A watch is an (object,mask) pair where the object is a file or directory and the mask is a bit mask of one or more inotify events that the application wishes to receive. See @@ -22,43 +26,52 @@ Watches are added via a path to the file. Watches on a directory will return events on any files inside of the directory. -Adding a watch is simple, +Adding a watch is simple: int wd = inotify_add_watch (fd, path, mask); -You can add a large number of files via something like - - for each file to watch { - int wd = inotify_add_watch (fd, file, mask); - } +Where "fd" is the return value from inotify_init(), path is the path to the +object to watch, and mask is the watch mask (see ). You can update an existing watch in the same manner, by passing in a new mask. -An existing watch is removed via the INOTIFY_IGNORE ioctl, for example +An existing watch is removed via - inotify_rm_watch (fd, wd); + int ret = inotify_rm_watch (fd, wd); Events are provided in the form of an inotify_event structure that is read(2) -from a inotify instance fd. The filename is of dynamic length and follows the -struct. It is of size len. The filename is padded with null bytes to ensure -proper alignment. This padding is reflected in len. +from a given inotify instance. The filename is of dynamic length and follows +the struct. It is of size len. The filename is padded with null bytes to +ensure proper alignment. This padding is reflected in len. You can slurp multiple events by passing a large buffer, for example size_t len = read (fd, buf, BUF_LEN); -Will return as many events as are available and fit in BUF_LEN. +Where "buf" is a pointer to an array of "inotify_event" structures at least +BUF_LEN bytes in size. The above example will return as many events as are +available and fit in BUF_LEN. -each inotify instance fd is also select()- and poll()-able. +Each inotify instance fd is also select()- and poll()-able. -You can find the size of the current event queue via the FIONREAD ioctl. +You can find the size of the current event queue via the standard FIONREAD +ioctl on the fd returned by inotify_init(). All watches are destroyed and cleaned up on close. -(ii) Internal Kernel Implementation +(ii) + +Prototypes: + + int inotify_init (void); + int inotify_add_watch (int fd, const char *path, __u32 mask); + int inotify_rm_watch (int fd, __u32 mask); + -Each open inotify instance is associated with an inotify_device structure. +(iii) Internal Kernel Implementation + +Each inotify instance is associated with an inotify_device structure. Each watch is associated with an inotify_watch structure. Watches are chained off of each associated device and each associated inode. @@ -66,7 +79,7 @@ off of each associated device and each associated inode. See fs/inotify.c for the locking and lifetime rules. -(iii) Rationale +(iv) Rationale Q: What is the design decision behind not tying the watch to the open fd of the watched object? @@ -75,9 +88,9 @@ A: Watches are associated with an open inotify device, not an open file. This solves the primary problem with dnotify: keeping the file open pins the file and thus, worse, pins the mount. Dnotify is therefore infeasible for use on a desktop system with removable media as the media cannot be - unmounted. + unmounted. Watching a file should not require that it be open. -Q: What is the design decision behind using an-fd-per-device as opposed to +Q: What is the design decision behind using an-fd-per-instance as opposed to an fd-per-watch? A: An fd-per-watch quickly consumes more file descriptors than are allowed, @@ -86,8 +99,8 @@ A: An fd-per-watch quickly consumes more file descriptors than are allowed, can use epoll, but requiring both is a silly and extraneous requirement. A watch consumes less memory than an open file, separating the number spaces is thus sensible. The current design is what user-space developers - want: Users initialize inotify, once, and add n watches, requiring but one fd - and no twiddling with fd limits. Initializing an inotify instance two + want: Users initialize inotify, once, and add n watches, requiring but one + fd and no twiddling with fd limits. Initializing an inotify instance two thousand times is silly. If we can implement user-space's preferences cleanly--and we can, the idr layer makes stuff like this trivial--then we should. @@ -111,9 +124,6 @@ A: An fd-per-watch quickly consumes more file descriptors than are allowed, example, love it. Trust me, I asked. It is not a surprise: Who'd want to manage and block on 1000 fd's via select? - - You'd have to manage the fd's, as an example: Call close() when you - received a delete event. - - No way to get out of band data. - 1024 is still too low. ;-) @@ -122,6 +132,11 @@ A: An fd-per-watch quickly consumes more file descriptors than are allowed, scales to 1000s of directories, juggling 1000s of fd's just does not seem the right interface. It is too heavy. + Additionally, it _is_ possible to more than one instance and + juggle more than one queue and thus more than one associated fd. There + need not be a one-fd-per-process mapping; it is one-fd-per-queue and a + process can easily want more than one queue. + Q: Why the system call approach? A: The poor user-space interface is the second biggest problem with dnotify. @@ -131,8 +146,6 @@ A: The poor user-space interface is the second biggest problem with dnotify. Obtaining the fd and managing the watches could have been done either via a device file or a family of new system calls. We decided to implement a family of system calls because that is the preffered approach for new kernel - features and it means our user interface requirements. - - Additionally, it _is_ possible to more than one instance and - juggle more than one queue and thus more than one associated fd. + interfaces. The only real difference was whether we wanted to use open(2) + and ioctl(2) or a couple of new system calls. System calls beat ioctls. -- cgit v1.2.3 From 661f83a67c2e360d5a4d2406cc28379c909f94bf Mon Sep 17 00:00:00 2001 From: Russell King Date: Sat, 16 Jul 2005 09:30:53 +0100 Subject: [PATCH] Serial: Move deprecation of register_serial forward to September I think it's about time to make the build a little more vocal about the expiry of these functions. Due to recent discussions with problems in the console initialisation vs power manglement, I'd like to move the date forward to September. Signed-off-by: Russell King --- Documentation/feature-removal-schedule.txt | 4 ++-- include/linux/serial.h | 6 ++++-- include/linux/serial_core.h | 5 +++-- 3 files changed, 9 insertions(+), 6 deletions(-) (limited to 'Documentation') diff --git a/Documentation/feature-removal-schedule.txt b/Documentation/feature-removal-schedule.txt index 12dde43fe65..8b1430b4665 100644 --- a/Documentation/feature-removal-schedule.txt +++ b/Documentation/feature-removal-schedule.txt @@ -103,11 +103,11 @@ Who: Jody McIntyre --------------------------- What: register_serial/unregister_serial -When: December 2005 +When: September 2005 Why: This interface does not allow serial ports to be registered against a struct device, and as such does not allow correct power management of such ports. 8250-based ports should use serial8250_register_port - and serial8250_unregister_port instead. + and serial8250_unregister_port, or platform devices instead. Who: Russell King --------------------------- diff --git a/include/linux/serial.h b/include/linux/serial.h index 00145822fb7..9f2d85284d0 100644 --- a/include/linux/serial.h +++ b/include/linux/serial.h @@ -174,9 +174,11 @@ struct serial_icounter_struct { #ifdef __KERNEL__ +#include + /* Export to allow PCMCIA to use this - Dave Hinds */ -extern int register_serial(struct serial_struct *req); -extern void unregister_serial(int line); +extern int __deprecated register_serial(struct serial_struct *req); +extern void __deprecated unregister_serial(int line); /* Allow architectures to override entries in serial8250_ports[] at run time: */ struct uart_port; /* forward declaration */ diff --git a/include/linux/serial_core.h b/include/linux/serial_core.h index d6025af7efa..30b64f3534f 100644 --- a/include/linux/serial_core.h +++ b/include/linux/serial_core.h @@ -122,6 +122,7 @@ #ifdef __KERNEL__ #include +#include #include #include #include @@ -359,8 +360,8 @@ struct tty_driver *uart_console_device(struct console *co, int *index); */ int uart_register_driver(struct uart_driver *uart); void uart_unregister_driver(struct uart_driver *uart); -void uart_unregister_port(struct uart_driver *reg, int line); -int uart_register_port(struct uart_driver *reg, struct uart_port *port); +void __deprecated uart_unregister_port(struct uart_driver *reg, int line); +int __deprecated uart_register_port(struct uart_driver *reg, struct uart_port *port); int uart_add_one_port(struct uart_driver *reg, struct uart_port *port); int uart_remove_one_port(struct uart_driver *reg, struct uart_port *port); int uart_match_port(struct uart_port *port1, struct uart_port *port2); -- cgit v1.2.3 From 878cf4e1c7be6bffde3ace888a65ac3d43c127bb Mon Sep 17 00:00:00 2001 From: Michael Burian Date: Sat, 16 Jul 2005 16:43:49 +0100 Subject: [PATCH] ARM: 2794/1: Add "Image" and "mach-types.h" to dontdiff list Patch from Michael Burian comment in "mach-types.h" tells that it should not be patched "Image" is a binary, just as zImage, uImage and friends are Signed-off-by: Michael Burian Signed-off-by: Russell King --- Documentation/dontdiff | 2 ++ 1 file changed, 2 insertions(+) (limited to 'Documentation') diff --git a/Documentation/dontdiff b/Documentation/dontdiff index d4fda25db86..b974cf595d0 100644 --- a/Documentation/dontdiff +++ b/Documentation/dontdiff @@ -41,6 +41,7 @@ COPYING CREDITS CVS ChangeSet +Image Kerntypes MODS.txt Module.symvers @@ -103,6 +104,7 @@ logo_*.c logo_*_clut224.c logo_*_mono.c lxdialog +mach-types.h make_times_h map maui_boot.h -- cgit v1.2.3 From ec0344a2c93c770fe1ef7cdccd8115a69ca100d1 Mon Sep 17 00:00:00 2001 From: Adrian Bunk Date: Wed, 27 Jul 2005 11:45:15 -0700 Subject: [PATCH] Documentation/Changes: document the required udev version Document that udev 058 is required. Signed-off-by: Adrian Bunk Signed-off-by: Andrew Morton Signed-off-by: Linus Torvalds --- Documentation/Changes | 1 + 1 file changed, 1 insertion(+) (limited to 'Documentation') diff --git a/Documentation/Changes b/Documentation/Changes index dfec7569d45..5eaab0441d7 100644 --- a/Documentation/Changes +++ b/Documentation/Changes @@ -65,6 +65,7 @@ o isdn4k-utils 3.1pre1 # isdnctrl 2>&1|grep version o nfs-utils 1.0.5 # showmount --version o procps 3.2.0 # ps --version o oprofile 0.9 # oprofiled --version +o udev 058 # udevinfo -V Kernel compilation ================== -- cgit v1.2.3 From 3f75daddb4fc6b695faa4e12e76894389e913dcb Mon Sep 17 00:00:00 2001 From: Hal Rosenstock Date: Wed, 27 Jul 2005 11:45:41 -0700 Subject: [PATCH] IB: User MAD ABI changes to support RMPP User MAD ABI changes to support RMPP Signed-off-by: Hal Rosenstock Cc: Roland Dreier Signed-off-by: Andrew Morton Signed-off-by: Linus Torvalds --- Documentation/infiniband/user_mad.txt | 53 ++++++++++++++++++++++++-------- drivers/infiniband/include/ib_user_mad.h | 28 +++++++++++++---- 2 files changed, 62 insertions(+), 19 deletions(-) (limited to 'Documentation') diff --git a/Documentation/infiniband/user_mad.txt b/Documentation/infiniband/user_mad.txt index cae0c83f1ee..750fe5e80eb 100644 --- a/Documentation/infiniband/user_mad.txt +++ b/Documentation/infiniband/user_mad.txt @@ -28,13 +28,37 @@ Creating MAD agents Receiving MADs - MADs are received using read(). The buffer passed to read() must be - large enough to hold at least one struct ib_user_mad. For example: - - struct ib_user_mad mad; - ret = read(fd, &mad, sizeof mad); - if (ret != sizeof mad) + MADs are received using read(). The receive side now supports + RMPP. The buffer passed to read() must be at least one + struct ib_user_mad + 256 bytes. For example: + + If the buffer passed is not large enough to hold the received + MAD (RMPP), the errno is set to ENOSPC and the length of the + buffer needed is set in mad.length. + + Example for normal MAD (non RMPP) reads: + struct ib_user_mad *mad; + mad = malloc(sizeof *mad + 256); + ret = read(fd, mad, sizeof *mad + 256); + if (ret != sizeof mad + 256) { + perror("read"); + free(mad); + } + + Example for RMPP reads: + struct ib_user_mad *mad; + mad = malloc(sizeof *mad + 256); + ret = read(fd, mad, sizeof *mad + 256); + if (ret == -ENOSPC)) { + length = mad.length; + free(mad); + mad = malloc(sizeof *mad + length); + ret = read(fd, mad, sizeof *mad + length); + } + if (ret < 0) { perror("read"); + free(mad); + } In addition to the actual MAD contents, the other struct ib_user_mad fields will be filled in with information on the received MAD. For @@ -50,18 +74,21 @@ Sending MADs MADs are sent using write(). The agent ID for sending should be filled into the id field of the MAD, the destination LID should be - filled into the lid field, and so on. For example: + filled into the lid field, and so on. The send side does support + RMPP so arbitrary length MAD can be sent. For example: + + struct ib_user_mad *mad; - struct ib_user_mad mad; + mad = malloc(sizeof *mad + mad_length); - /* fill in mad.data */ + /* fill in mad->data */ - mad.id = my_agent; /* req.id from agent registration */ - mad.lid = my_dest; /* in network byte order... */ + mad->hdr.id = my_agent; /* req.id from agent registration */ + mad->hdr.lid = my_dest; /* in network byte order... */ /* etc. */ - ret = write(fd, &mad, sizeof mad); - if (ret != sizeof mad) + ret = write(fd, &mad, sizeof *mad + mad_length); + if (ret != sizeof *mad + mad_length) perror("write"); Setting IsSM Capability Bit diff --git a/drivers/infiniband/include/ib_user_mad.h b/drivers/infiniband/include/ib_user_mad.h index 06ad4a6075f..a9a56b50aac 100644 --- a/drivers/infiniband/include/ib_user_mad.h +++ b/drivers/infiniband/include/ib_user_mad.h @@ -1,5 +1,6 @@ /* * Copyright (c) 2004 Topspin Communications. All rights reserved. + * Copyright (c) 2005 Voltaire, Inc. All rights reserved. * * This software is available to you under a choice of one of two * licenses. You may choose to be licensed under the terms of the GNU @@ -29,7 +30,7 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. * - * $Id: ib_user_mad.h 1389 2004-12-27 22:56:47Z roland $ + * $Id: ib_user_mad.h 2814 2005-07-06 19:14:09Z halr $ */ #ifndef IB_USER_MAD_H @@ -42,7 +43,7 @@ * Increment this value if any changes that break userspace ABI * compatibility are made. */ -#define IB_USER_MAD_ABI_VERSION 2 +#define IB_USER_MAD_ABI_VERSION 5 /* * Make sure that all structs defined in this file remain laid out so @@ -51,13 +52,13 @@ */ /** - * ib_user_mad - MAD packet - * @data - Contents of MAD + * ib_user_mad_hdr - MAD packet header * @id - ID of agent MAD received with/to be sent with * @status - 0 on successful receive, ETIMEDOUT if no response * received (transaction ID in data[] will be set to TID of original * request) (ignored on send) * @timeout_ms - Milliseconds to wait for response (unset on receive) + * @retries - Number of automatic retries to attempt * @qpn - Remote QP number received from/to be sent to * @qkey - Remote Q_Key to be sent with (unset on receive) * @lid - Remote lid received from/to be sent to @@ -72,11 +73,12 @@ * * All multi-byte quantities are stored in network (big endian) byte order. */ -struct ib_user_mad { - __u8 data[256]; +struct ib_user_mad_hdr { __u32 id; __u32 status; __u32 timeout_ms; + __u32 retries; + __u32 length; __u32 qpn; __u32 qkey; __u16 lid; @@ -90,6 +92,17 @@ struct ib_user_mad { __u32 flow_label; }; +/** + * ib_user_mad - MAD packet + * @hdr - MAD packet header + * @data - Contents of MAD + * + */ +struct ib_user_mad { + struct ib_user_mad_hdr hdr; + __u8 data[0]; +}; + /** * ib_user_mad_reg_req - MAD registration request * @id - Set by the kernel; used to identify agent in future requests. @@ -103,6 +116,8 @@ struct ib_user_mad { * management class to receive. * @oui: Indicates IEEE OUI when mgmt_class is a vendor class * in the range from 0x30 to 0x4f. Otherwise not used. + * @rmpp_version: If set, indicates the RMPP version used. + * */ struct ib_user_mad_reg_req { __u32 id; @@ -111,6 +126,7 @@ struct ib_user_mad_reg_req { __u8 mgmt_class; __u8 mgmt_class_version; __u8 oui[3]; + __u8 rmpp_version; }; #define IB_IOCTL_MAGIC 0x1b -- cgit v1.2.3 From 617b586bca4eda775f93915b8efd586dddf7903c Mon Sep 17 00:00:00 2001 From: Hal Rosenstock Date: Wed, 27 Jul 2005 11:45:47 -0700 Subject: [PATCH] IB: Add core locking documentation to Infiniband Add core locking documentation to Infiniband Signed-off-by: Roland Dreier Signed-off-by: Hal Rosenstock Cc: Roland Dreier Signed-off-by: Andrew Morton Signed-off-by: Linus Torvalds --- Documentation/infiniband/core_locking.txt | 114 ++++++++++++++++++++++++++++++ 1 file changed, 114 insertions(+) create mode 100644 Documentation/infiniband/core_locking.txt (limited to 'Documentation') diff --git a/Documentation/infiniband/core_locking.txt b/Documentation/infiniband/core_locking.txt new file mode 100644 index 00000000000..e1678542279 --- /dev/null +++ b/Documentation/infiniband/core_locking.txt @@ -0,0 +1,114 @@ +INFINIBAND MIDLAYER LOCKING + + This guide is an attempt to make explicit the locking assumptions + made by the InfiniBand midlayer. It describes the requirements on + both low-level drivers that sit below the midlayer and upper level + protocols that use the midlayer. + +Sleeping and interrupt context + + With the following exceptions, a low-level driver implementation of + all of the methods in struct ib_device may sleep. The exceptions + are any methods from the list: + + create_ah + modify_ah + query_ah + destroy_ah + bind_mw + post_send + post_recv + poll_cq + req_notify_cq + map_phys_fmr + + which may not sleep and must be callable from any context. + + The corresponding functions exported to upper level protocol + consumers: + + ib_create_ah + ib_modify_ah + ib_query_ah + ib_destroy_ah + ib_bind_mw + ib_post_send + ib_post_recv + ib_req_notify_cq + ib_map_phys_fmr + + are therefore safe to call from any context. + + In addition, the function + + ib_dispatch_event + + used by low-level drivers to dispatch asynchronous events through + the midlayer is also safe to call from any context. + +Reentrancy + + All of the methods in struct ib_device exported by a low-level + driver must be fully reentrant. The low-level driver is required to + perform all synchronization necessary to maintain consistency, even + if multiple function calls using the same object are run + simultaneously. + + The IB midlayer does not perform any serialization of function calls. + + Because low-level drivers are reentrant, upper level protocol + consumers are not required to perform any serialization. However, + some serialization may be required to get sensible results. For + example, a consumer may safely call ib_poll_cq() on multiple CPUs + simultaneously. However, the ordering of the work completion + information between different calls of ib_poll_cq() is not defined. + +Callbacks + + A low-level driver must not perform a callback directly from the + same callchain as an ib_device method call. For example, it is not + allowed for a low-level driver to call a consumer's completion event + handler directly from its post_send method. Instead, the low-level + driver should defer this callback by, for example, scheduling a + tasklet to perform the callback. + + The low-level driver is responsible for ensuring that multiple + completion event handlers for the same CQ are not called + simultaneously. The driver must guarantee that only one CQ event + handler for a given CQ is running at a time. In other words, the + following situation is not allowed: + + CPU1 CPU2 + + low-level driver -> + consumer CQ event callback: + /* ... */ + ib_req_notify_cq(cq, ...); + low-level driver -> + /* ... */ consumer CQ event callback: + /* ... */ + return from CQ event handler + + The context in which completion event and asynchronous event + callbacks run is not defined. Depending on the low-level driver, it + may be process context, softirq context, or interrupt context. + Upper level protocol consumers may not sleep in a callback. + +Hot-plug + + A low-level driver announces that a device is ready for use by + consumers when it calls ib_register_device(), all initialization + must be complete before this call. The device must remain usable + until the driver's call to ib_unregister_device() has returned. + + A low-level driver must call ib_register_device() and + ib_unregister_device() from process context. It must not hold any + semaphores that could cause deadlock if a consumer calls back into + the driver across these calls. + + An upper level protocol consumer may begin using an IB device as + soon as the add method of its struct ib_client is called for that + device. A consumer must finish all cleanup and free all resources + relating to a device before returning from the remove method. + + A consumer is permitted to sleep in its add and remove methods. -- cgit v1.2.3 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 +++++ sound/pci/hda/patch_realtek.c | 14 +++++++++++++- 2 files changed, 18 insertions(+), 1 deletion(-) (limited to 'Documentation') diff --git a/Documentation/sound/alsa/ALSA-Configuration.txt b/Documentation/sound/alsa/ALSA-Configuration.txt index 104a994b828..dfe44cf655d 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 diff --git a/sound/pci/hda/patch_realtek.c b/sound/pci/hda/patch_realtek.c index ed16ce817dd..9b4339a004f 100644 --- a/sound/pci/hda/patch_realtek.c +++ b/sound/pci/hda/patch_realtek.c @@ -40,6 +40,7 @@ enum { ALC880_W810, ALC880_Z71V, ALC880_AUTO, + ALC880_6ST, ALC880_6ST_DIG, ALC880_F1734, ALC880_ASUS, @@ -1559,7 +1560,9 @@ static struct hda_board_config alc880_cfg_tbl[] = { { .modelname = "z71v", .config = ALC880_Z71V }, { .pci_subvendor = 0x1043, .pci_subdevice = 0x1964, .config = ALC880_Z71V }, - { .modelname = "6statack-digout", .config = ALC880_6ST_DIG }, + { .modelname = "6stack", .config = ALC880_6ST }, + + { .modelname = "6stack-digout", .config = ALC880_6ST_DIG }, { .pci_subvendor = 0x2668, .pci_subdevice = 0x8086, .config = ALC880_6ST_DIG }, { .pci_subvendor = 0x8086, .pci_subdevice = 0x2668, .config = ALC880_6ST_DIG }, { .pci_subvendor = 0x1462, .pci_subdevice = 0x1150, .config = ALC880_6ST_DIG }, @@ -1646,6 +1649,15 @@ static struct alc_config_preset alc880_presets[] = { .channel_mode = alc880_fivestack_modes, .input_mux = &alc880_capture_source, }, + [ALC880_6ST] = { + .mixers = { alc880_six_stack_mixer }, + .init_verbs = { alc880_volume_init_verbs, alc880_pin_6stack_init_verbs }, + .num_dacs = ARRAY_SIZE(alc880_6st_dac_nids), + .dac_nids = alc880_6st_dac_nids, + .num_channel_mode = ARRAY_SIZE(alc880_sixstack_modes), + .channel_mode = alc880_sixstack_modes, + .input_mux = &alc880_6stack_capture_source, + }, [ALC880_6ST_DIG] = { .mixers = { alc880_six_stack_mixer }, .init_verbs = { alc880_volume_init_verbs, alc880_pin_6stack_init_verbs }, -- cgit v1.2.3 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 dfe44cf655d..fe95bb2ae7f 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 3611e298834..5d1b0b762ef 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 f35dfa1af09..54971f01e96 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 62d6fa12814..3a82161d3b2 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 4462c04a4e8..70754aa3dd1 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 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 fe95bb2ae7f..94887dbf1b4 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 f0fce1daf67..25a8a558ef9 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 6809cc92d27..3cd89c67c2f 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 00000000000..941c7b1e7eb --- /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 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 94887dbf1b4..a18ecb92b35 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 34c1740aa6e..2e4a5e0d16d 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 f74ec28e106..103f136926d 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 00000000000..46052304e23 --- /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 00000000000..b1eb53b02ea --- /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 00000000000..43517597cab --- /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 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 59ccc63838c..403e7b4dcdd 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 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 b9e6be00cad..476c0c22fbb 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 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 3cea1387527..f39c9d714db 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 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 00000000000..2c81305090d --- /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