aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/media/dvb/siano/smsdvb.c
diff options
context:
space:
mode:
authorMichael Krufky <mkrufky@linuxtv.org>2008-06-19 19:35:21 -0400
committerMauro Carvalho Chehab <mchehab@infradead.org>2008-07-20 06:22:37 -0400
commita0c0abcb1fdb316dee3a38cff9843d7d094c327c (patch)
tree6f6284afd5940e43df2b8004b3bed789fef09ba6 /drivers/media/dvb/siano/smsdvb.c
parent068d6c0f5d6c67d0e93f8e214897ddd64746be4e (diff)
V4L/DVB (8294): sms1xxx: move message formatting into printk macros
Signed-off-by: Michael Krufky <mkrufky@linuxtv.org> Signed-off-by: Mauro Carvalho Chehab <mchehab@infradead.org>
Diffstat (limited to 'drivers/media/dvb/siano/smsdvb.c')
-rw-r--r--drivers/media/dvb/siano/smsdvb.c35
1 files changed, 15 insertions, 20 deletions
diff --git a/drivers/media/dvb/siano/smsdvb.c b/drivers/media/dvb/siano/smsdvb.c
index 57ab7db56a65..a54e9c77edd8 100644
--- a/drivers/media/dvb/siano/smsdvb.c
+++ b/drivers/media/dvb/siano/smsdvb.c
@@ -113,7 +113,7 @@ static int smsdvb_start_feed(struct dvb_demux_feed *feed)
113 container_of(feed->demux, struct smsdvb_client_t, demux); 113 container_of(feed->demux, struct smsdvb_client_t, demux);
114 struct SmsMsgData_ST PidMsg; 114 struct SmsMsgData_ST PidMsg;
115 115
116 sms_debug("%s add pid %d(%x)\n", __func__, 116 sms_debug("add pid %d(%x)",
117 feed->pid, feed->pid); 117 feed->pid, feed->pid);
118 118
119 PidMsg.xMsgHeader.msgSrcId = DVBT_BDA_CONTROL_MSG_ID; 119 PidMsg.xMsgHeader.msgSrcId = DVBT_BDA_CONTROL_MSG_ID;
@@ -133,7 +133,7 @@ static int smsdvb_stop_feed(struct dvb_demux_feed *feed)
133 container_of(feed->demux, struct smsdvb_client_t, demux); 133 container_of(feed->demux, struct smsdvb_client_t, demux);
134 struct SmsMsgData_ST PidMsg; 134 struct SmsMsgData_ST PidMsg;
135 135
136 sms_debug("%s remove pid %d(%x)\n", __func__, 136 sms_debug("remove pid %d(%x)",
137 feed->pid, feed->pid); 137 feed->pid, feed->pid);
138 138
139 PidMsg.xMsgHeader.msgSrcId = DVBT_BDA_CONTROL_MSG_ID; 139 PidMsg.xMsgHeader.msgSrcId = DVBT_BDA_CONTROL_MSG_ID;
@@ -220,7 +220,7 @@ static int smsdvb_read_snr(struct dvb_frontend *fe, u16 *snr)
220static int smsdvb_get_tune_settings(struct dvb_frontend *fe, 220static int smsdvb_get_tune_settings(struct dvb_frontend *fe,
221 struct dvb_frontend_tune_settings *tune) 221 struct dvb_frontend_tune_settings *tune)
222{ 222{
223 sms_debug("%s\n", __func__); 223 sms_debug("");
224 224
225 tune->min_delay_ms = 400; 225 tune->min_delay_ms = 400;
226 tune->step_size = 250000; 226 tune->step_size = 250000;
@@ -247,7 +247,7 @@ static int smsdvb_set_frontend(struct dvb_frontend *fe,
247 Msg.Data[0] = fep->frequency; 247 Msg.Data[0] = fep->frequency;
248 Msg.Data[2] = 12000000; 248 Msg.Data[2] = 12000000;
249 249
250 sms_debug("%s freq %d band %d\n", __func__, 250 sms_debug("freq %d band %d",
251 fep->frequency, fep->u.ofdm.bandwidth); 251 fep->frequency, fep->u.ofdm.bandwidth);
252 252
253 switch (fep->u.ofdm.bandwidth) { 253 switch (fep->u.ofdm.bandwidth) {
@@ -268,7 +268,7 @@ static int smsdvb_get_frontend(struct dvb_frontend *fe,
268 struct smsdvb_client_t *client = 268 struct smsdvb_client_t *client =
269 container_of(fe, struct smsdvb_client_t, frontend); 269 container_of(fe, struct smsdvb_client_t, frontend);
270 270
271 sms_debug("%s\n", __func__); 271 sms_debug("");
272 272
273 /* todo: */ 273 /* todo: */
274 memcpy(fep, &client->fe_params, 274 memcpy(fep, &client->fe_params,
@@ -322,14 +322,14 @@ int smsdvb_hotplug(struct smscore_device_t *coredev,
322 return 0; 322 return 0;
323 323
324 if (smscore_get_device_mode(coredev) != 4) { 324 if (smscore_get_device_mode(coredev) != 4) {
325 sms_err("%sSMS Device mode is not set for " 325 sms_err("SMS Device mode is not set for "
326 "DVB operation.\n", __func__); 326 "DVB operation.");
327 return 0; 327 return 0;
328 } 328 }
329 329
330 client = kzalloc(sizeof(struct smsdvb_client_t), GFP_KERNEL); 330 client = kzalloc(sizeof(struct smsdvb_client_t), GFP_KERNEL);
331 if (!client) { 331 if (!client) {
332 sms_info("%s kmalloc() failed\n", __func__); 332 sms_info("kmalloc() failed");
333 return -ENOMEM; 333 return -ENOMEM;
334 } 334 }
335 335
@@ -339,8 +339,7 @@ int smsdvb_hotplug(struct smscore_device_t *coredev,
339 smscore_get_board_id(coredev))->name, 339 smscore_get_board_id(coredev))->name,
340 THIS_MODULE, device, adapter_nr); 340 THIS_MODULE, device, adapter_nr);
341 if (rc < 0) { 341 if (rc < 0) {
342 sms_err("%s dvb_register_adapter() failed %d\n", 342 sms_err("dvb_register_adapter() failed %d", rc);
343 __func__, rc);
344 goto adapter_error; 343 goto adapter_error;
345 } 344 }
346 345
@@ -353,8 +352,7 @@ int smsdvb_hotplug(struct smscore_device_t *coredev,
353 352
354 rc = dvb_dmx_init(&client->demux); 353 rc = dvb_dmx_init(&client->demux);
355 if (rc < 0) { 354 if (rc < 0) {
356 sms_err("%s dvb_dmx_init failed %d\n\n", 355 sms_err("dvb_dmx_init failed %d", rc);
357 __func__, rc);
358 goto dvbdmx_error; 356 goto dvbdmx_error;
359 } 357 }
360 358
@@ -365,8 +363,7 @@ int smsdvb_hotplug(struct smscore_device_t *coredev,
365 363
366 rc = dvb_dmxdev_init(&client->dmxdev, &client->adapter); 364 rc = dvb_dmxdev_init(&client->dmxdev, &client->adapter);
367 if (rc < 0) { 365 if (rc < 0) {
368 sms_err("%s dvb_dmxdev_init failed %d\n", 366 sms_err("dvb_dmxdev_init failed %d", rc);
369 __func__, rc);
370 goto dmxdev_error; 367 goto dmxdev_error;
371 } 368 }
372 369
@@ -376,8 +373,7 @@ int smsdvb_hotplug(struct smscore_device_t *coredev,
376 373
377 rc = dvb_register_frontend(&client->adapter, &client->frontend); 374 rc = dvb_register_frontend(&client->adapter, &client->frontend);
378 if (rc < 0) { 375 if (rc < 0) {
379 sms_err("%s frontend registration failed %d\n", 376 sms_err("frontend registration failed %d", rc);
380 __func__, rc);
381 goto frontend_error; 377 goto frontend_error;
382 } 378 }
383 379
@@ -389,8 +385,7 @@ int smsdvb_hotplug(struct smscore_device_t *coredev,
389 385
390 rc = smscore_register_client(coredev, &params, &client->smsclient); 386 rc = smscore_register_client(coredev, &params, &client->smsclient);
391 if (rc < 0) { 387 if (rc < 0) {
392 sms_info("%s smscore_register_client() failed %d\n", 388 sms_info("smscore_register_client() failed %d", rc);
393 __func__, rc);
394 goto client_error; 389 goto client_error;
395 } 390 }
396 391
@@ -405,7 +400,7 @@ int smsdvb_hotplug(struct smscore_device_t *coredev,
405 400
406 kmutex_unlock(&g_smsdvb_clientslock); 401 kmutex_unlock(&g_smsdvb_clientslock);
407 402
408 sms_info("%s success\n", __func__); 403 sms_info("success");
409 404
410 return 0; 405 return 0;
411 406
@@ -435,7 +430,7 @@ int smsdvb_register(void)
435 430
436 rc = smscore_register_hotplug(smsdvb_hotplug); 431 rc = smscore_register_hotplug(smsdvb_hotplug);
437 432
438 sms_info("%s\n", __func__); 433 sms_debug("");
439 434
440 return rc; 435 return rc;
441} 436}
444'>1444 1445 1446 1447 1448 1449 1450 1451 1452 1453 1454 1455 1456 1457 1458 1459 1460 1461 1462 1463 1464 1465 1466 1467 1468 1469 1470 1471 1472 1473 1474 1475 1476 1477 1478 1479 1480 1481 1482 1483 1484 1485 1486 1487 1488 1489 1490 1491 1492 1493 1494 1495 1496 1497 1498 1499 1500 1501 1502 1503 1504 1505 1506 1507 1508 1509 1510 1511 1512 1513 1514 1515 1516 1517 1518 1519 1520 1521 1522 1523 1524 1525 1526 1527 1528 1529 1530 1531 1532 1533 1534 1535 1536 1537 1538 1539 1540 1541 1542 1543 1544 1545 1546 1547 1548 1549 1550 1551 1552 1553 1554 1555 1556 1557 1558 1559 1560 1561 1562 1563 1564 1565 1566 1567 1568 1569 1570 1571 1572 1573 1574 1575 1576 1577 1578 1579 1580 1581 1582 1583 1584 1585 1586 1587 1588 1589 1590 1591 1592 1593 1594 1595 1596 1597 1598 1599 1600 1601 1602 1603 1604 1605 1606 1607 1608 1609 1610 1611 1612 1613 1614 1615 1616 1617 1618 1619 1620 1621 1622 1623 1624 1625 1626 1627 1628 1629 1630 1631 1632 1633 1634 1635 1636 1637 1638 1639 1640 1641 1642 1643 1644 1645 1646 1647 1648 1649 1650 1651 1652 1653 1654 1655 1656 1657 1658 1659 1660 1661 1662 1663 1664 1665 1666 1667 1668 1669 1670 1671 1672 1673 1674 1675 1676 1677 1678 1679 1680 1681 1682 1683 1684 1685 1686 1687 1688 1689 1690 1691 1692 1693 1694 1695 1696 1697 1698 1699 1700 1701 1702 1703 1704 1705 1706 1707 1708 1709 1710 1711 1712 1713 1714 1715 1716 1717 1718 1719 1720 1721 1722 1723 1724 1725 1726 1727 1728 1729 1730 1731 1732 1733 1734 1735 1736 1737 1738 1739 1740 1741 1742 1743 1744 1745 1746 1747 1748 1749 1750 1751 1752 1753 1754 1755 1756 1757 1758 1759 1760 1761 1762 1763 1764 1765 1766 1767 1768 1769 1770 1771 1772 1773 1774 1775 1776 1777 1778 1779 1780 1781 1782 1783 1784 1785 1786 1787 1788 1789 1790 1791 1792 1793 1794 1795 1796 1797 1798 1799 1800 1801 1802 1803 1804 1805 1806 1807 1808 1809 1810 1811 1812 1813 1814 1815 1816 1817 1818 1819 1820 1821 1822 1823 1824 1825 1826 1827 1828 1829 1830 1831 1832 1833 1834 1835 1836 1837 1838 1839 1840 1841 1842 1843 1844 1845
/*
 *  Copyright (c) by Jaroslav Kysela <perex@perex.cz>
 *  Routines for control of CS4231(A)/CS4232/InterWave & compatible chips
 *
 *  Bugs:
 *     - sometimes record brokes playback with WSS portion of 
 *       Yamaha OPL3-SA3 chip
 *     - CS4231 (GUS MAX) - still trouble with occasional noises
 *                        - broken initialization?
 *
 *   This program is free software; you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation; either version 2 of the License, or
 *   (at your option) any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program; if not, write to the Free Software
 *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
 *
 */

#include <linux/delay.h>
#include <linux/pm.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/slab.h>
#include <linux/ioport.h>
#include <sound/core.h>
#include <sound/cs4231.h>
#include <sound/pcm_params.h>

#include <asm/io.h>
#include <asm/dma.h>
#include <asm/irq.h>

MODULE_AUTHOR("Jaroslav Kysela <perex@perex.cz>");
MODULE_DESCRIPTION("Routines for control of CS4231(A)/CS4232/InterWave & compatible chips");
MODULE_LICENSE("GPL");

#if 0
#define SNDRV_DEBUG_MCE
#endif

/*
 *  Some variables
 */

static unsigned char freq_bits[14] = {
	/* 5510 */	0x00 | CS4231_XTAL2,
	/* 6620 */	0x0E | CS4231_XTAL2,
	/* 8000 */	0x00 | CS4231_XTAL1,
	/* 9600 */	0x0E | CS4231_XTAL1,
	/* 11025 */	0x02 | CS4231_XTAL2,
	/* 16000 */	0x02 | CS4231_XTAL1,
	/* 18900 */	0x04 | CS4231_XTAL2,
	/* 22050 */	0x06 | CS4231_XTAL2,
	/* 27042 */	0x04 | CS4231_XTAL1,
	/* 32000 */	0x06 | CS4231_XTAL1,
	/* 33075 */	0x0C | CS4231_XTAL2,
	/* 37800 */	0x08 | CS4231_XTAL2,
	/* 44100 */	0x0A | CS4231_XTAL2,
	/* 48000 */	0x0C | CS4231_XTAL1
};

static unsigned int rates[14] = {
	5510, 6620, 8000, 9600, 11025, 16000, 18900, 22050,
	27042, 32000, 33075, 37800, 44100, 48000
};

static struct snd_pcm_hw_constraint_list hw_constraints_rates = {
	.count = ARRAY_SIZE(rates),
	.list = rates,
	.mask = 0,
};

static int snd_cs4231_xrate(struct snd_pcm_runtime *runtime)
{
	return snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, &hw_constraints_rates);
}

static unsigned char snd_cs4231_original_image[32] =
{
	0x00,			/* 00/00 - lic */
	0x00,			/* 01/01 - ric */
	0x9f,			/* 02/02 - la1ic */
	0x9f,			/* 03/03 - ra1ic */
	0x9f,			/* 04/04 - la2ic */
	0x9f,			/* 05/05 - ra2ic */
	0xbf,			/* 06/06 - loc */
	0xbf,			/* 07/07 - roc */
	0x20,			/* 08/08 - pdfr */
	CS4231_AUTOCALIB,	/* 09/09 - ic */
	0x00,			/* 0a/10 - pc */
	0x00,			/* 0b/11 - ti */
	CS4231_MODE2,		/* 0c/12 - mi */
	0xfc,			/* 0d/13 - lbc */
	0x00,			/* 0e/14 - pbru */
	0x00,			/* 0f/15 - pbrl */
	0x80,			/* 10/16 - afei */
	0x01,			/* 11/17 - afeii */
	0x9f,			/* 12/18 - llic */
	0x9f,			/* 13/19 - rlic */
	0x00,			/* 14/20 - tlb */
	0x00,			/* 15/21 - thb */
	0x00,			/* 16/22 - la3mic/reserved */
	0x00,			/* 17/23 - ra3mic/reserved */
	0x00,			/* 18/24 - afs */
	0x00,			/* 19/25 - lamoc/version */
	0xcf,			/* 1a/26 - mioc */
	0x00,			/* 1b/27 - ramoc/reserved */
	0x20,			/* 1c/28 - cdfr */
	0x00,			/* 1d/29 - res4 */
	0x00,			/* 1e/30 - cbru */
	0x00,			/* 1f/31 - cbrl */
};

/*
 *  Basic I/O functions
 */

static inline void cs4231_outb(struct snd_cs4231 *chip, u8 offset, u8 val)
{
	outb(val, chip->port + offset);
}

static inline u8 cs4231_inb(struct snd_cs4231 *chip, u8 offset)
{
	return inb(chip->port + offset);
}

static void snd_cs4231_wait(struct snd_cs4231 *chip)
{
	int timeout;

	for (timeout = 250;
	     timeout > 0 && (cs4231_inb(chip, CS4231P(REGSEL)) & CS4231_INIT);
	     timeout--)
	     	udelay(100);
}

static void snd_cs4231_outm(struct snd_cs4231 *chip, unsigned char reg,
			    unsigned char mask, unsigned char value)
{
	unsigned char tmp = (chip->image[reg] & mask) | value;

	snd_cs4231_wait(chip);
#ifdef CONFIG_SND_DEBUG
	if (cs4231_inb(chip, CS4231P(REGSEL)) & CS4231_INIT)
		snd_printk("outm: auto calibration time out - reg = 0x%x, value = 0x%x\n", reg, value);
#endif
	chip->image[reg] = tmp;
	if (!chip->calibrate_mute) {
		cs4231_outb(chip, CS4231P(REGSEL), chip->mce_bit | reg);
		wmb();
		cs4231_outb(chip, CS4231P(REG), tmp);
		mb();
	}
}

static void snd_cs4231_dout(struct snd_cs4231 *chip, unsigned char reg, unsigned char value)
{
	int timeout;

	for (timeout = 250;
	     timeout > 0 && (cs4231_inb(chip, CS4231P(REGSEL)) & CS4231_INIT);
	     timeout--)
	     	udelay(10);
	cs4231_outb(chip, CS4231P(REGSEL), chip->mce_bit | reg);
	cs4231_outb(chip, CS4231P(REG), value);
	mb();
}

void snd_cs4231_out(struct snd_cs4231 *chip, unsigned char reg, unsigned char value)
{
	snd_cs4231_wait(chip);
#ifdef CONFIG_SND_DEBUG
	if (cs4231_inb(chip, CS4231P(REGSEL)) & CS4231_INIT)
		snd_printk("out: auto calibration time out - reg = 0x%x, value = 0x%x\n", reg, value);
#endif
	cs4231_outb(chip, CS4231P(REGSEL), chip->mce_bit | reg);
	cs4231_outb(chip, CS4231P(REG), value);
	chip->image[reg] = value;
	mb();
	snd_printdd("codec out - reg 0x%x = 0x%x\n",
			chip->mce_bit | reg, value);
}

unsigned char snd_cs4231_in(struct snd_cs4231 *chip, unsigned char reg)
{
	snd_cs4231_wait(chip);
#ifdef CONFIG_SND_DEBUG
	if (cs4231_inb(chip, CS4231P(REGSEL)) & CS4231_INIT)
		snd_printk("in: auto calibration time out - reg = 0x%x\n", reg);
#endif
	cs4231_outb(chip, CS4231P(REGSEL), chip->mce_bit | reg);
	mb();
	return cs4231_inb(chip, CS4231P(REG));
}

void snd_cs4236_ext_out(struct snd_cs4231 *chip, unsigned char reg, unsigned char val)
{
	cs4231_outb(chip, CS4231P(REGSEL), chip->mce_bit | 0x17);
	cs4231_outb(chip, CS4231P(REG), reg | (chip->image[CS4236_EXT_REG] & 0x01));
	cs4231_outb(chip, CS4231P(REG), val);
	chip->eimage[CS4236_REG(reg)] = val;
#if 0
	printk("ext out : reg = 0x%x, val = 0x%x\n", reg, val);
#endif
}

unsigned char snd_cs4236_ext_in(struct snd_cs4231 *chip, unsigned char reg)
{
	cs4231_outb(chip, CS4231P(REGSEL), chip->mce_bit | 0x17);
	cs4231_outb(chip, CS4231P(REG), reg | (chip->image[CS4236_EXT_REG] & 0x01));
#if 1
	return cs4231_inb(chip, CS4231P(REG));
#else
	{
		unsigned char res;
		res = cs4231_inb(chip, CS4231P(REG));
		printk("ext in : reg = 0x%x, val = 0x%x\n", reg, res);
		return res;
	}
#endif
}

#if 0

static void snd_cs4231_debug(struct snd_cs4231 *chip)
{
	printk("CS4231 REGS:      INDEX = 0x%02x  ", cs4231_inb(chip, CS4231P(REGSEL)));
	printk("                 STATUS = 0x%02x\n", cs4231_inb(chip, CS4231P(STATUS)));
	printk("  0x00: left input      = 0x%02x  ", snd_cs4231_in(chip, 0x00));
	printk("  0x10: alt 1 (CFIG 2)  = 0x%02x\n", snd_cs4231_in(chip, 0x10));
	printk("  0x01: right input     = 0x%02x  ", snd_cs4231_in(chip, 0x01));
	printk("  0x11: alt 2 (CFIG 3)  = 0x%02x\n", snd_cs4231_in(chip, 0x11));
	printk("  0x02: GF1 left input  = 0x%02x  ", snd_cs4231_in(chip, 0x02));
	printk("  0x12: left line in    = 0x%02x\n", snd_cs4231_in(chip, 0x12));
	printk("  0x03: GF1 right input = 0x%02x  ", snd_cs4231_in(chip, 0x03));
	printk("  0x13: right line in   = 0x%02x\n", snd_cs4231_in(chip, 0x13));
	printk("  0x04: CD left input   = 0x%02x  ", snd_cs4231_in(chip, 0x04));
	printk("  0x14: timer low       = 0x%02x\n", snd_cs4231_in(chip, 0x14));
	printk("  0x05: CD right input  = 0x%02x  ", snd_cs4231_in(chip, 0x05));
	printk("  0x15: timer high      = 0x%02x\n", snd_cs4231_in(chip, 0x15));
	printk("  0x06: left output     = 0x%02x  ", snd_cs4231_in(chip, 0x06));
	printk("  0x16: left MIC (PnP)  = 0x%02x\n", snd_cs4231_in(chip, 0x16));
	printk("  0x07: right output    = 0x%02x  ", snd_cs4231_in(chip, 0x07));
	printk("  0x17: right MIC (PnP) = 0x%02x\n", snd_cs4231_in(chip, 0x17));
	printk("  0x08: playback format = 0x%02x  ", snd_cs4231_in(chip, 0x08));
	printk("  0x18: IRQ status      = 0x%02x\n", snd_cs4231_in(chip, 0x18));
	printk("  0x09: iface (CFIG 1)  = 0x%02x  ", snd_cs4231_in(chip, 0x09));
	printk("  0x19: left line out   = 0x%02x\n", snd_cs4231_in(chip, 0x19));
	printk("  0x0a: pin control     = 0x%02x  ", snd_cs4231_in(chip, 0x0a));
	printk("  0x1a: mono control    = 0x%02x\n", snd_cs4231_in(chip, 0x1a));
	printk("  0x0b: init & status   = 0x%02x  ", snd_cs4231_in(chip, 0x0b));
	printk("  0x1b: right line out  = 0x%02x\n", snd_cs4231_in(chip, 0x1b));
	printk("  0x0c: revision & mode = 0x%02x  ", snd_cs4231_in(chip, 0x0c));
	printk("  0x1c: record format   = 0x%02x\n", snd_cs4231_in(chip, 0x1c));
	printk("  0x0d: loopback        = 0x%02x  ", snd_cs4231_in(chip, 0x0d));
	printk("  0x1d: var freq (PnP)  = 0x%02x\n", snd_cs4231_in(chip, 0x1d));
	printk("  0x0e: ply upr count   = 0x%02x  ", snd_cs4231_in(chip, 0x0e));
	printk("  0x1e: ply lwr count   = 0x%02x\n", snd_cs4231_in(chip, 0x1e));
	printk("  0x0f: rec upr count   = 0x%02x  ", snd_cs4231_in(chip, 0x0f));
	printk("  0x1f: rec lwr count   = 0x%02x\n", snd_cs4231_in(chip, 0x1f));
}

#endif

/*
 *  CS4231 detection / MCE routines
 */

static void snd_cs4231_busy_wait(struct snd_cs4231 *chip)
{
	int timeout;

	/* huh.. looks like this sequence is proper for CS4231A chip (GUS MAX) */
	for (timeout = 5; timeout > 0; timeout--)
		cs4231_inb(chip, CS4231P(REGSEL));
	/* end of cleanup sequence */
	for (timeout = 250;
	     timeout > 0 && (cs4231_inb(chip, CS4231P(REGSEL)) & CS4231_INIT);
	     timeout--)
	     	udelay(10);
}

void snd_cs4231_mce_up(struct snd_cs4231 *chip)
{
	unsigned long flags;
	int timeout;

	snd_cs4231_wait(chip);
#ifdef CONFIG_SND_DEBUG
	if (cs4231_inb(chip, CS4231P(REGSEL)) & CS4231_INIT)
		snd_printk("mce_up - auto calibration time out (0)\n");
#endif
	spin_lock_irqsave(&chip->reg_lock, flags);
	chip->mce_bit |= CS4231_MCE;
	timeout = cs4231_inb(chip, CS4231P(REGSEL));
	if (timeout == 0x80)
		snd_printk("mce_up [0x%lx]: serious init problem - codec still busy\n", chip->port);
	if (!(timeout & CS4231_MCE))
		cs4231_outb(chip, CS4231P(REGSEL), chip->mce_bit | (timeout & 0x1f));
	spin_unlock_irqrestore(&chip->reg_lock, flags);
}

void snd_cs4231_mce_down(struct snd_cs4231 *chip)
{
	unsigned long flags;
	unsigned long end_time;
	int timeout;

	snd_cs4231_busy_wait(chip);

#ifdef CONFIG_SND_DEBUG
	if (cs4231_inb(chip, CS4231P(REGSEL)) & CS4231_INIT)
		snd_printk("mce_down [0x%lx] - auto calibration time out (0)\n", (long)CS4231P(REGSEL));
#endif
	spin_lock_irqsave(&chip->reg_lock, flags);
	chip->mce_bit &= ~CS4231_MCE;
	timeout = cs4231_inb(chip, CS4231P(REGSEL));
	cs4231_outb(chip, CS4231P(REGSEL), chip->mce_bit | (timeout & 0x1f));
	spin_unlock_irqrestore(&chip->reg_lock, flags);
	if (timeout == 0x80)
		snd_printk("mce_down [0x%lx]: serious init problem - codec still busy\n", chip->port);
	if ((timeout & CS4231_MCE) == 0 ||
	    !(chip->hardware & (CS4231_HW_CS4231_MASK | CS4231_HW_CS4232_MASK))) {
		return;
	}

	/*
	 * Wait for (possible -- during init auto-calibration may not be set)
	 * calibration process to start. Needs upto 5 sample periods on AD1848
	 * which at the slowest possible rate of 5.5125 kHz means 907 us.
	 */
	msleep(1);

	snd_printdd("(1) jiffies = %lu\n", jiffies);

	/* check condition up to 250 ms */
	end_time = jiffies + msecs_to_jiffies(250);
	while (snd_cs4231_in(chip, CS4231_TEST_INIT) &
		CS4231_CALIB_IN_PROGRESS) {

		if (time_after(jiffies, end_time)) {
			snd_printk(KERN_ERR "mce_down - "
					"auto calibration time out (2)\n");
			return;
		}
		msleep(1);
	}

	snd_printdd("(2) jiffies = %lu\n", jiffies);

	/* check condition up to 100 ms */
	end_time = jiffies + msecs_to_jiffies(100);
	while (cs4231_inb(chip, CS4231P(REGSEL)) & CS4231_INIT) {
		if (time_after(jiffies, end_time)) {
			snd_printk(KERN_ERR "mce_down - auto calibration time out (3)\n");
			return;
		}
		msleep(1);
	}

	snd_printdd("(3) jiffies = %lu\n", jiffies);
	snd_printd("mce_down - exit = 0x%x\n", cs4231_inb(chip, CS4231P(REGSEL)));
}

static unsigned int snd_cs4231_get_count(unsigned char format, unsigned int size)
{
	switch (format & 0xe0) {
	case CS4231_LINEAR_16:
	case CS4231_LINEAR_16_BIG:
		size >>= 1;
		break;
	case CS4231_ADPCM_16:
		return size >> 2;
	}
	if (format & CS4231_STEREO)
		size >>= 1;
	return size;
}

static int snd_cs4231_trigger(struct snd_pcm_substream *substream,
			      int cmd)
{
	struct snd_cs4231 *chip = snd_pcm_substream_chip(substream);
	int result = 0;
	unsigned int what;
	struct snd_pcm_substream *s;
	int do_start;

#if 0
	printk("codec trigger!!! - what = %i, enable = %i, status = 0x%x\n", what, enable, cs4231_inb(chip, CS4231P(STATUS)));
#endif

	switch (cmd) {
	case SNDRV_PCM_TRIGGER_START:
	case SNDRV_PCM_TRIGGER_RESUME:
		do_start = 1; break;
	case SNDRV_PCM_TRIGGER_STOP:
	case SNDRV_PCM_TRIGGER_SUSPEND:
		do_start = 0; break;
	default:
		return -EINVAL;
	}

	what = 0;
	snd_pcm_group_for_each_entry(s, substream) {
		if (s == chip->playback_substream) {
			what |= CS4231_PLAYBACK_ENABLE;
			snd_pcm_trigger_done(s, substream);
		} else if (s == chip->capture_substream) {
			what |= CS4231_RECORD_ENABLE;
			snd_pcm_trigger_done(s, substream);
		}
	}
	spin_lock(&chip->reg_lock);
	if (do_start) {
		chip->image[CS4231_IFACE_CTRL] |= what;
		if (chip->trigger)
			chip->trigger(chip, what, 1);
	} else {
		chip->image[CS4231_IFACE_CTRL] &= ~what;
		if (chip->trigger)
			chip->trigger(chip, what, 0);
	}
	snd_cs4231_out(chip, CS4231_IFACE_CTRL, chip->image[CS4231_IFACE_CTRL]);
	spin_unlock(&chip->reg_lock);
#if 0
	snd_cs4231_debug(chip);
#endif
	return result;
}

/*
 *  CODEC I/O
 */

static unsigned char snd_cs4231_get_rate(unsigned int rate)
{
	int i;

	for (i = 0; i < ARRAY_SIZE(rates); i++)
		if (rate == rates[i])
			return freq_bits[i];
	// snd_BUG();
	return freq_bits[ARRAY_SIZE(rates) - 1];
}

static unsigned char snd_cs4231_get_format(struct snd_cs4231 *chip,
				           int format,
                                           int channels)
{
	unsigned char rformat;

	rformat = CS4231_LINEAR_8;
	switch (format) {
	case SNDRV_PCM_FORMAT_MU_LAW:	rformat = CS4231_ULAW_8; break;
	case SNDRV_PCM_FORMAT_A_LAW:	rformat = CS4231_ALAW_8; break;
	case SNDRV_PCM_FORMAT_S16_LE:	rformat = CS4231_LINEAR_16; break;
	case SNDRV_PCM_FORMAT_S16_BE:	rformat = CS4231_LINEAR_16_BIG; break;
	case SNDRV_PCM_FORMAT_IMA_ADPCM:	rformat = CS4231_ADPCM_16; break;
	}
	if (channels > 1)
		rformat |= CS4231_STEREO;
#if 0
	snd_printk("get_format: 0x%x (mode=0x%x)\n", format, mode);
#endif
	return rformat;
}

static void snd_cs4231_calibrate_mute(struct snd_cs4231 *chip, int mute)
{
	unsigned long flags;

	mute = mute ? 1 : 0;
	spin_lock_irqsave(&chip->reg_lock, flags);
	if (chip->calibrate_mute == mute) {
		spin_unlock_irqrestore(&chip->reg_lock, flags);
		return;
	}
	if (!mute) {
		snd_cs4231_dout(chip, CS4231_LEFT_INPUT, chip->image[CS4231_LEFT_INPUT]);
		snd_cs4231_dout(chip, CS4231_RIGHT_INPUT, chip->image[CS4231_RIGHT_INPUT]);
		snd_cs4231_dout(chip, CS4231_LOOPBACK, chip->image[CS4231_LOOPBACK]);
	}
	snd_cs4231_dout(chip, CS4231_AUX1_LEFT_INPUT, mute ? 0x80 : chip->image[CS4231_AUX1_LEFT_INPUT]);
	snd_cs4231_dout(chip, CS4231_AUX1_RIGHT_INPUT, mute ? 0x80 : chip->image[CS4231_AUX1_RIGHT_INPUT]);
	snd_cs4231_dout(chip, CS4231_AUX2_LEFT_INPUT, mute ? 0x80 : chip->image[CS4231_AUX2_LEFT_INPUT]);
	snd_cs4231_dout(chip, CS4231_AUX2_RIGHT_INPUT, mute ? 0x80 : chip->image[CS4231_AUX2_RIGHT_INPUT]);
	snd_cs4231_dout(chip, CS4231_LEFT_OUTPUT, mute ? 0x80 : chip->image[CS4231_LEFT_OUTPUT]);
	snd_cs4231_dout(chip, CS4231_RIGHT_OUTPUT, mute ? 0x80 : chip->image[CS4231_RIGHT_OUTPUT]);
	snd_cs4231_dout(chip, CS4231_LEFT_LINE_IN, mute ? 0x80 : chip->image[CS4231_LEFT_LINE_IN]);
	snd_cs4231_dout(chip, CS4231_RIGHT_LINE_IN, mute ? 0x80 : chip->image[CS4231_RIGHT_LINE_IN]);
	snd_cs4231_dout(chip, CS4231_MONO_CTRL, mute ? 0xc0 : chip->image[CS4231_MONO_CTRL]);
	if (chip->hardware == CS4231_HW_INTERWAVE) {
		snd_cs4231_dout(chip, CS4231_LEFT_MIC_INPUT, mute ? 0x80 : chip->image[CS4231_LEFT_MIC_INPUT]);
		snd_cs4231_dout(chip, CS4231_RIGHT_MIC_INPUT, mute ? 0x80 : chip->image[CS4231_RIGHT_MIC_INPUT]);		
		snd_cs4231_dout(chip, CS4231_LINE_LEFT_OUTPUT, mute ? 0x80 : chip->image[CS4231_LINE_LEFT_OUTPUT]);
		snd_cs4231_dout(chip, CS4231_LINE_RIGHT_OUTPUT, mute ? 0x80 : chip->image[CS4231_LINE_RIGHT_OUTPUT]);
	}
	chip->calibrate_mute = mute;
	spin_unlock_irqrestore(&chip->reg_lock, flags);
}

static void snd_cs4231_playback_format(struct snd_cs4231 *chip,
				       struct snd_pcm_hw_params *params,
				       unsigned char pdfr)
{
	unsigned long flags;
	int full_calib = 1;

	mutex_lock(&chip->mce_mutex);
	snd_cs4231_calibrate_mute(chip, 1);
	if (chip->hardware == CS4231_HW_CS4231A ||
	    (chip->hardware & CS4231_HW_CS4232_MASK)) {
		spin_lock_irqsave(&chip->reg_lock, flags);
		if ((chip->image[CS4231_PLAYBK_FORMAT] & 0x0f) == (pdfr & 0x0f)) {	/* rate is same? */
			snd_cs4231_out(chip, CS4231_ALT_FEATURE_1, chip->image[CS4231_ALT_FEATURE_1] | 0x10);
			snd_cs4231_out(chip, CS4231_PLAYBK_FORMAT, chip->image[CS4231_PLAYBK_FORMAT] = pdfr);
			snd_cs4231_out(chip, CS4231_ALT_FEATURE_1, chip->image[CS4231_ALT_FEATURE_1] &= ~0x10);
			udelay(100); /* Fixes audible clicks at least on GUS MAX */
			full_calib = 0;
		}
		spin_unlock_irqrestore(&chip->reg_lock, flags);
	}
	if (full_calib) {
		snd_cs4231_mce_up(chip);
		spin_lock_irqsave(&chip->reg_lock, flags);
		if (chip->hardware != CS4231_HW_INTERWAVE && !chip->single_dma) {
			snd_cs4231_out(chip, CS4231_PLAYBK_FORMAT,
					(chip->image[CS4231_IFACE_CTRL] & CS4231_RECORD_ENABLE) ?
					(pdfr & 0xf0) | (chip->image[CS4231_REC_FORMAT] & 0x0f) :
				        pdfr);
		} else {
			snd_cs4231_out(chip, CS4231_PLAYBK_FORMAT, chip->image[CS4231_PLAYBK_FORMAT] = pdfr);
		}
		spin_unlock_irqrestore(&chip->reg_lock, flags);
		if (chip->hardware == CS4231_HW_OPL3SA2)
			udelay(100);	/* this seems to help */
		snd_cs4231_mce_down(chip);
	}
	snd_cs4231_calibrate_mute(chip, 0);
	mutex_unlock(&chip->mce_mutex);
}

static void snd_cs4231_capture_format(struct snd_cs4231 *chip,
				      struct snd_pcm_hw_params *params,
                                      unsigned char cdfr)
{
	unsigned long flags;
	int full_calib = 1;

	mutex_lock(&chip->mce_mutex);
	snd_cs4231_calibrate_mute(chip, 1);
	if (chip->hardware == CS4231_HW_CS4231A ||
	    (chip->hardware & CS4231_HW_CS4232_MASK)) {
		spin_lock_irqsave(&chip->reg_lock, flags);
		if ((chip->image[CS4231_PLAYBK_FORMAT] & 0x0f) == (cdfr & 0x0f) ||	/* rate is same? */
		    (chip->image[CS4231_IFACE_CTRL] & CS4231_PLAYBACK_ENABLE)) {
			snd_cs4231_out(chip, CS4231_ALT_FEATURE_1, chip->image[CS4231_ALT_FEATURE_1] | 0x20);
			snd_cs4231_out(chip, CS4231_REC_FORMAT, chip->image[CS4231_REC_FORMAT] = cdfr);
			snd_cs4231_out(chip, CS4231_ALT_FEATURE_1, chip->image[CS4231_ALT_FEATURE_1] &= ~0x20);
			full_calib = 0;
		}
		spin_unlock_irqrestore(&chip->reg_lock, flags);
	}
	if (full_calib) {
		snd_cs4231_mce_up(chip);
		spin_lock_irqsave(&chip->reg_lock, flags);
		if (chip->hardware != CS4231_HW_INTERWAVE) {
			if (!(chip->image[CS4231_IFACE_CTRL] & CS4231_PLAYBACK_ENABLE)) {
				snd_cs4231_out(chip, CS4231_PLAYBK_FORMAT,
					       ((chip->single_dma ? cdfr : chip->image[CS4231_PLAYBK_FORMAT]) & 0xf0) |
					       (cdfr & 0x0f));
				spin_unlock_irqrestore(&chip->reg_lock, flags);
				snd_cs4231_mce_down(chip);
				snd_cs4231_mce_up(chip);
				spin_lock_irqsave(&chip->reg_lock, flags);
			}
		}
		snd_cs4231_out(chip, CS4231_REC_FORMAT, cdfr);
		spin_unlock_irqrestore(&chip->reg_lock, flags);
		snd_cs4231_mce_down(chip);
	}
	snd_cs4231_calibrate_mute(chip, 0);
	mutex_unlock(&chip->mce_mutex);
}

/*
 *  Timer interface
 */

static unsigned long snd_cs4231_timer_resolution(struct snd_timer * timer)
{
	struct snd_cs4231 *chip = snd_timer_chip(timer);
	if (chip->hardware & CS4231_HW_CS4236B_MASK)
		return 14467;
	else
		return chip->image[CS4231_PLAYBK_FORMAT] & 1 ? 9969 : 9920;
}

static int snd_cs4231_timer_start(struct snd_timer * timer)
{
	unsigned long flags;
	unsigned int ticks;
	struct snd_cs4231 *chip = snd_timer_chip(timer);
	spin_lock_irqsave(&chip->reg_lock, flags);
	ticks = timer->sticks;
	if ((chip->image[CS4231_ALT_FEATURE_1] & CS4231_TIMER_ENABLE) == 0 ||
	    (unsigned char)(ticks >> 8) != chip->image[CS4231_TIMER_HIGH] ||
	    (unsigned char)ticks != chip->image[CS4231_TIMER_LOW]) {
		snd_cs4231_out(chip, CS4231_TIMER_HIGH, chip->image[CS4231_TIMER_HIGH] = (unsigned char) (ticks >> 8));
		snd_cs4231_out(chip, CS4231_TIMER_LOW, chip->image[CS4231_TIMER_LOW] = (unsigned char) ticks);
		snd_cs4231_out(chip, CS4231_ALT_FEATURE_1, chip->image[CS4231_ALT_FEATURE_1] | CS4231_TIMER_ENABLE);
	}
	spin_unlock_irqrestore(&chip->reg_lock, flags);
	return 0;
}

static int snd_cs4231_timer_stop(struct snd_timer * timer)
{
	unsigned long flags;
	struct snd_cs4231 *chip = snd_timer_chip(timer);
	spin_lock_irqsave(&chip->reg_lock, flags);
	snd_cs4231_out(chip, CS4231_ALT_FEATURE_1, chip->image[CS4231_ALT_FEATURE_1] &= ~CS4231_TIMER_ENABLE);
	spin_unlock_irqrestore(&chip->reg_lock, flags);
	return 0;
}

static void snd_cs4231_init(struct snd_cs4231 *chip)
{
	unsigned long flags;

	snd_cs4231_mce_down(chip);

#ifdef SNDRV_DEBUG_MCE
	snd_printk("init: (1)\n");
#endif
	snd_cs4231_mce_up(chip);
	spin_lock_irqsave(&chip->reg_lock, flags);
	chip->image[CS4231_IFACE_CTRL] &= ~(CS4231_PLAYBACK_ENABLE | CS4231_PLAYBACK_PIO |
			     CS4231_RECORD_ENABLE | CS4231_RECORD_PIO |
			     CS4231_CALIB_MODE);
	chip->image[CS4231_IFACE_CTRL] |= CS4231_AUTOCALIB;
	snd_cs4231_out(chip, CS4231_IFACE_CTRL, chip->image[CS4231_IFACE_CTRL]);
	spin_unlock_irqrestore(&chip->reg_lock, flags);
	snd_cs4231_mce_down(chip);

#ifdef SNDRV_DEBUG_MCE
	snd_printk("init: (2)\n");
#endif

	snd_cs4231_mce_up(chip);
	spin_lock_irqsave(&chip->reg_lock, flags);
	snd_cs4231_out(chip, CS4231_ALT_FEATURE_1, chip->image[CS4231_ALT_FEATURE_1]);
	spin_unlock_irqrestore(&chip->reg_lock, flags);
	snd_cs4231_mce_down(chip);

#ifdef SNDRV_DEBUG_MCE
	snd_printk("init: (3) - afei = 0x%x\n", chip->image[CS4231_ALT_FEATURE_1]);
#endif

	spin_lock_irqsave(&chip->reg_lock, flags);
	snd_cs4231_out(chip, CS4231_ALT_FEATURE_2, chip->image[CS4231_ALT_FEATURE_2]);
	spin_unlock_irqrestore(&chip->reg_lock, flags);

	snd_cs4231_mce_up(chip);
	spin_lock_irqsave(&chip->reg_lock, flags);
	snd_cs4231_out(chip, CS4231_PLAYBK_FORMAT, chip->image[CS4231_PLAYBK_FORMAT]);
	spin_unlock_irqrestore(&chip->reg_lock, flags);
	snd_cs4231_mce_down(chip);

#ifdef SNDRV_DEBUG_MCE
	snd_printk("init: (4)\n");
#endif

	snd_cs4231_mce_up(chip);
	spin_lock_irqsave(&chip->reg_lock, flags);
	snd_cs4231_out(chip, CS4231_REC_FORMAT, chip->image[CS4231_REC_FORMAT]);
	spin_unlock_irqrestore(&chip->reg_lock, flags);
	snd_cs4231_mce_down(chip);

#ifdef SNDRV_DEBUG_MCE
	snd_printk("init: (5)\n");
#endif
}

static int snd_cs4231_open(struct snd_cs4231 *chip, unsigned int mode)
{
	unsigned long flags;

	mutex_lock(&chip->open_mutex);
	if ((chip->mode & mode) ||
	    ((chip->mode & CS4231_MODE_OPEN) && chip->single_dma)) {
		mutex_unlock(&chip->open_mutex);
		return -EAGAIN;
	}
	if (chip->mode & CS4231_MODE_OPEN) {
		chip->mode |= mode;
		mutex_unlock(&chip->open_mutex);
		return 0;
	}
	/* ok. now enable and ack CODEC IRQ */
	spin_lock_irqsave(&chip->reg_lock, flags);
	snd_cs4231_out(chip, CS4231_IRQ_STATUS, CS4231_PLAYBACK_IRQ |
		       CS4231_RECORD_IRQ |
		       CS4231_TIMER_IRQ);
	snd_cs4231_out(chip, CS4231_IRQ_STATUS, 0);
	cs4231_outb(chip, CS4231P(STATUS), 0);	/* clear IRQ */
	cs4231_outb(chip, CS4231P(STATUS), 0);	/* clear IRQ */
	chip->image[CS4231_PIN_CTRL] |= CS4231_IRQ_ENABLE;
	snd_cs4231_out(chip, CS4231_PIN_CTRL, chip->image[CS4231_PIN_CTRL]);
	snd_cs4231_out(chip, CS4231_IRQ_STATUS, CS4231_PLAYBACK_IRQ |
		       CS4231_RECORD_IRQ |
		       CS4231_TIMER_IRQ);
	snd_cs4231_out(chip, CS4231_IRQ_STATUS, 0);
	spin_unlock_irqrestore(&chip->reg_lock, flags);

	chip->mode = mode;
	mutex_unlock(&chip->open_mutex);
	return 0;
}

static void snd_cs4231_close(struct snd_cs4231 *chip, unsigned int mode)
{
	unsigned long flags;

	mutex_lock(&chip->open_mutex);
	chip->mode &= ~mode;
	if (chip->mode & CS4231_MODE_OPEN) {
		mutex_unlock(&chip->open_mutex);
		return;
	}
	snd_cs4231_calibrate_mute(chip, 1);

	/* disable IRQ */
	spin_lock_irqsave(&chip->reg_lock, flags);
	snd_cs4231_out(chip, CS4231_IRQ_STATUS, 0);
	cs4231_outb(chip, CS4231P(STATUS), 0);	/* clear IRQ */
	cs4231_outb(chip, CS4231P(STATUS), 0);	/* clear IRQ */
	chip->image[CS4231_PIN_CTRL] &= ~CS4231_IRQ_ENABLE;
	snd_cs4231_out(chip, CS4231_PIN_CTRL, chip->image[CS4231_PIN_CTRL]);

	/* now disable record & playback */

	if (chip->image[CS4231_IFACE_CTRL] & (CS4231_PLAYBACK_ENABLE | CS4231_PLAYBACK_PIO |
					       CS4231_RECORD_ENABLE | CS4231_RECORD_PIO)) {
		spin_unlock_irqrestore(&chip->reg_lock, flags);
		snd_cs4231_mce_up(chip);
		spin_lock_irqsave(&chip->reg_lock, flags);
		chip->image[CS4231_IFACE_CTRL] &= ~(CS4231_PLAYBACK_ENABLE | CS4231_PLAYBACK_PIO |
						     CS4231_RECORD_ENABLE | CS4231_RECORD_PIO);
		snd_cs4231_out(chip, CS4231_IFACE_CTRL, chip->image[CS4231_IFACE_CTRL]);
		spin_unlock_irqrestore(&chip->reg_lock, flags);
		snd_cs4231_mce_down(chip);
		spin_lock_irqsave(&chip->reg_lock, flags);
	}

	/* clear IRQ again */
	snd_cs4231_out(chip, CS4231_IRQ_STATUS, 0);
	cs4231_outb(chip, CS4231P(STATUS), 0);	/* clear IRQ */
	cs4231_outb(chip, CS4231P(STATUS), 0);	/* clear IRQ */
	spin_unlock_irqrestore(&chip->reg_lock, flags);

	snd_cs4231_calibrate_mute(chip, 0);

	chip->mode = 0;
	mutex_unlock(&chip->open_mutex);
}

/*
 *  timer open/close
 */

static int snd_cs4231_timer_open(struct snd_timer * timer)
{
	struct snd_cs4231 *chip = snd_timer_chip(timer);
	snd_cs4231_open(chip, CS4231_MODE_TIMER);
	return 0;
}

static int snd_cs4231_timer_close(struct snd_timer * timer)
{
	struct snd_cs4231 *chip = snd_timer_chip(timer);
	snd_cs4231_close(chip, CS4231_MODE_TIMER);
	return 0;
}

static struct snd_timer_hardware snd_cs4231_timer_table =
{
	.flags =	SNDRV_TIMER_HW_AUTO,
	.resolution =	9945,
	.ticks =	65535,
	.open =		snd_cs4231_timer_open,
	.close =	snd_cs4231_timer_close,
	.c_resolution = snd_cs4231_timer_resolution,
	.start =	snd_cs4231_timer_start,
	.stop =		snd_cs4231_timer_stop,
};

/*
 *  ok.. exported functions..
 */

static int snd_cs4231_playback_hw_params(struct snd_pcm_substream *substream,
					 struct snd_pcm_hw_params *hw_params)
{
	struct snd_cs4231 *chip = snd_pcm_substream_chip(substream);
	unsigned char new_pdfr;
	int err;

	if ((err = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params))) < 0)
		return err;
	new_pdfr = snd_cs4231_get_format(chip, params_format(hw_params), params_channels(hw_params)) |
		   snd_cs4231_get_rate(params_rate(hw_params));
	chip->set_playback_format(chip, hw_params, new_pdfr);
	return 0;
}

static int snd_cs4231_playback_hw_free(struct snd_pcm_substream *substream)
{
	return snd_pcm_lib_free_pages(substream);
}

static int snd_cs4231_playback_prepare(struct snd_pcm_substream *substream)
{
	struct snd_cs4231 *chip = snd_pcm_substream_chip(substream);
	struct snd_pcm_runtime *runtime = substream->runtime;
	unsigned long flags;
	unsigned int size = snd_pcm_lib_buffer_bytes(substream);
	unsigned int count = snd_pcm_lib_period_bytes(substream);

	spin_lock_irqsave(&chip->reg_lock, flags);
	chip->p_dma_size = size;
	chip->image[CS4231_IFACE_CTRL] &= ~(CS4231_PLAYBACK_ENABLE | CS4231_PLAYBACK_PIO);
	snd_dma_program(chip->dma1, runtime->dma_addr, size, DMA_MODE_WRITE | DMA_AUTOINIT);
	count = snd_cs4231_get_count(chip->image[CS4231_PLAYBK_FORMAT], count) - 1;
	snd_cs4231_out(chip, CS4231_PLY_LWR_CNT, (unsigned char) count);
	snd_cs4231_out(chip, CS4231_PLY_UPR_CNT, (unsigned char) (count >> 8));
	spin_unlock_irqrestore(&chip->reg_lock, flags);
#if 0
	snd_cs4231_debug(chip);
#endif
	return 0;
}

static int snd_cs4231_capture_hw_params(struct snd_pcm_substream *substream,
					struct snd_pcm_hw_params *hw_params)
{
	struct snd_cs4231 *chip = snd_pcm_substream_chip(substream);
	unsigned char new_cdfr;
	int err;

	if ((err = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params))) < 0)
		return err;
	new_cdfr = snd_cs4231_get_format(chip, params_format(hw_params), params_channels(hw_params)) |
		   snd_cs4231_get_rate(params_rate(hw_params));
	chip->set_capture_format(chip, hw_params, new_cdfr);
	return 0;
}

static int snd_cs4231_capture_hw_free(struct snd_pcm_substream *substream)
{
	return snd_pcm_lib_free_pages(substream);
}

static int snd_cs4231_capture_prepare(struct snd_pcm_substream *substream)
{
	struct snd_cs4231 *chip = snd_pcm_substream_chip(substream);
	struct snd_pcm_runtime *runtime = substream->runtime;
	unsigned long flags;
	unsigned int size = snd_pcm_lib_buffer_bytes(substream);
	unsigned int count = snd_pcm_lib_period_bytes(substream);

	spin_lock_irqsave(&chip->reg_lock, flags);
	chip->c_dma_size = size;
	chip->image[CS4231_IFACE_CTRL] &= ~(CS4231_RECORD_ENABLE | CS4231_RECORD_PIO);
	snd_dma_program(chip->dma2, runtime->dma_addr, size, DMA_MODE_READ | DMA_AUTOINIT);
	count = snd_cs4231_get_count(chip->image[CS4231_REC_FORMAT], count) - 1;
	if (chip->single_dma && chip->hardware != CS4231_HW_INTERWAVE) {
		snd_cs4231_out(chip, CS4231_PLY_LWR_CNT, (unsigned char) count);
		snd_cs4231_out(chip, CS4231_PLY_UPR_CNT, (unsigned char) (count >> 8));
	} else {
		snd_cs4231_out(chip, CS4231_REC_LWR_CNT, (unsigned char) count);
		snd_cs4231_out(chip, CS4231_REC_UPR_CNT, (unsigned char) (count >> 8));
	}
	spin_unlock_irqrestore(&chip->reg_lock, flags);
	return 0;
}

static void snd_cs4231_overrange(struct snd_cs4231 *chip)
{
	unsigned long flags;
	unsigned char res;

	spin_lock_irqsave(&chip->reg_lock, flags);
	res = snd_cs4231_in(chip, CS4231_TEST_INIT);
	spin_unlock_irqrestore(&chip->reg_lock, flags);
	if (res & (0x08 | 0x02))	/* detect overrange only above 0dB; may be user selectable? */
		chip->capture_substream->runtime->overrange++;
}

irqreturn_t snd_cs4231_interrupt(int irq, void *dev_id)
{
	struct snd_cs4231 *chip = dev_id;
	unsigned char status;

	status = snd_cs4231_in(chip, CS4231_IRQ_STATUS);
	if (status & CS4231_TIMER_IRQ) {
		if (chip->timer)
			snd_timer_interrupt(chip->timer, chip->timer->sticks);
	}		
	if (chip->single_dma && chip->hardware != CS4231_HW_INTERWAVE) {
		if (status & CS4231_PLAYBACK_IRQ) {
			if (chip->mode & CS4231_MODE_PLAY) {
				if (chip->playback_substream)
					snd_pcm_period_elapsed(chip->playback_substream);
			}
			if (chip->mode & CS4231_MODE_RECORD) {
				if (chip->capture_substream) {
					snd_cs4231_overrange(chip);
					snd_pcm_period_elapsed(chip->capture_substream);
				}
			}
		}
	} else {
		if (status & CS4231_PLAYBACK_IRQ) {
			if (chip->playback_substream)
				snd_pcm_period_elapsed(chip->playback_substream);
		}
		if (status & CS4231_RECORD_IRQ) {
			if (chip->capture_substream) {
				snd_cs4231_overrange(chip);
				snd_pcm_period_elapsed(chip->capture_substream);
			}
		}
	}

	spin_lock(&chip->reg_lock);
	snd_cs4231_outm(chip, CS4231_IRQ_STATUS, ~CS4231_ALL_IRQS | ~status, 0);
	spin_unlock(&chip->reg_lock);
	return IRQ_HANDLED;
}

static snd_pcm_uframes_t snd_cs4231_playback_pointer(struct snd_pcm_substream *substream)
{
	struct snd_cs4231 *chip = snd_pcm_substream_chip(substream);
	size_t ptr;

	if (!(chip->image[CS4231_IFACE_CTRL] & CS4231_PLAYBACK_ENABLE))
		return 0;
	ptr = snd_dma_pointer(chip->dma1, chip->p_dma_size);
	return bytes_to_frames(substream->runtime, ptr);
}

static snd_pcm_uframes_t snd_cs4231_capture_pointer(struct snd_pcm_substream *substream)
{
	struct snd_cs4231 *chip = snd_pcm_substream_chip(substream);
	size_t ptr;
	
	if (!(chip->image[CS4231_IFACE_CTRL] & CS4231_RECORD_ENABLE))
		return 0;
	ptr = snd_dma_pointer(chip->dma2, chip->c_dma_size);
	return bytes_to_frames(substream->runtime, ptr);
}

/*

 */

static int snd_cs4231_probe(struct snd_cs4231 *chip)
{
	unsigned long flags;
	int i, id, rev;
	unsigned char *ptr;
	unsigned int hw;

#if 0
	snd_cs4231_debug(chip);
#endif
	id = 0;
	for (i = 0; i < 50; i++) {
		mb();
		if (cs4231_inb(chip, CS4231P(REGSEL)) & CS4231_INIT)
			udelay(2000);
		else {
			spin_lock_irqsave(&chip->reg_lock, flags);
			snd_cs4231_out(chip, CS4231_MISC_INFO, CS4231_MODE2);
			id = snd_cs4231_in(chip, CS4231_MISC_INFO) & 0x0f;
			spin_unlock_irqrestore(&chip->reg_lock, flags);
			if (id == 0x0a)
				break;	/* this is valid value */
		}
	}
	snd_printdd("cs4231: port = 0x%lx, id = 0x%x\n", chip->port, id);
	if (id != 0x0a)
		return -ENODEV;	/* no valid device found */

	if (((hw = chip->hardware) & CS4231_HW_TYPE_MASK) == CS4231_HW_DETECT) {
		rev = snd_cs4231_in(chip, CS4231_VERSION) & 0xe7;
		snd_printdd("CS4231: VERSION (I25) = 0x%x\n", rev);
		if (rev == 0x80) {
			unsigned char tmp = snd_cs4231_in(chip, 23);
			snd_cs4231_out(chip, 23, ~tmp);
			if (snd_cs4231_in(chip, 23) != tmp)
				chip->hardware = CS4231_HW_AD1845;
			else
				chip->hardware = CS4231_HW_CS4231;
		} else if (rev == 0xa0) {
			chip->hardware = CS4231_HW_CS4231A;
		} else if (rev == 0xa2) {
			chip->hardware = CS4231_HW_CS4232;
		} else if (rev == 0xb2) {
			chip->hardware = CS4231_HW_CS4232A;
		} else if (rev == 0x83) {
			chip->hardware = CS4231_HW_CS4236;
		} else if (rev == 0x03) {
			chip->hardware = CS4231_HW_CS4236B;
		} else {
			snd_printk("unknown CS chip with version 0x%x\n", rev);
			return -ENODEV;		/* unknown CS4231 chip? */
		}
	}
	spin_lock_irqsave(&chip->reg_lock, flags);
	cs4231_inb(chip, CS4231P(STATUS));	/* clear any pendings IRQ */
	cs4231_outb(chip, CS4231P(STATUS), 0);
	mb();
	spin_unlock_irqrestore(&chip->reg_lock, flags);

	chip->image[CS4231_MISC_INFO] = CS4231_MODE2;
	switch (chip->hardware) {
	case CS4231_HW_INTERWAVE:
		chip->image[CS4231_MISC_INFO] = CS4231_IW_MODE3;
		break;
	case CS4231_HW_CS4235:
	case CS4231_HW_CS4236B:
	case CS4231_HW_CS4237B:
	case CS4231_HW_CS4238B:
	case CS4231_HW_CS4239:
		if (hw == CS4231_HW_DETECT3)
			chip->image[CS4231_MISC_INFO] = CS4231_4236_MODE3;
		else
			chip->hardware = CS4231_HW_CS4236;
		break;
	}

	chip->image[CS4231_IFACE_CTRL] =
	    (chip->image[CS4231_IFACE_CTRL] & ~CS4231_SINGLE_DMA) |
	    (chip->single_dma ? CS4231_SINGLE_DMA : 0);
	chip->image[CS4231_ALT_FEATURE_1] = 0x80;
	chip->image[CS4231_ALT_FEATURE_2] = chip->hardware == CS4231_HW_INTERWAVE ? 0xc2 : 0x01;
	ptr = (unsigned char *) &chip->image;
	snd_cs4231_mce_down(chip);
	spin_lock_irqsave(&chip->reg_lock, flags);
	for (i = 0; i < 32; i++)	/* ok.. fill all CS4231 registers */
		snd_cs4231_out(chip, i, *ptr++);
	spin_unlock_irqrestore(&chip->reg_lock, flags);
	snd_cs4231_mce_up(chip);
	snd_cs4231_mce_down(chip);

	mdelay(2);

	/* ok.. try check hardware version for CS4236+ chips */
	if ((hw & CS4231_HW_TYPE_MASK) == CS4231_HW_DETECT) {
		if (chip->hardware == CS4231_HW_CS4236B) {
			rev = snd_cs4236_ext_in(chip, CS4236_VERSION);
			snd_cs4236_ext_out(chip, CS4236_VERSION, 0xff);
			id = snd_cs4236_ext_in(chip, CS4236_VERSION);
			snd_cs4236_ext_out(chip, CS4236_VERSION, rev);
			snd_printdd("CS4231: ext version; rev = 0x%x, id = 0x%x\n", rev, id);
			if ((id & 0x1f) == 0x1d) {	/* CS4235 */
				chip->hardware = CS4231_HW_CS4235;
				switch (id >> 5) {
				case 4:
				case 5:
				case 6:
					break;
				default:
					snd_printk("unknown CS4235 chip (enhanced version = 0x%x)\n", id);
				}
			} else if ((id & 0x1f) == 0x0b) {	/* CS4236/B */
				switch (id >> 5) {
				case 4:
				case 5:
				case 6:
				case 7:
					chip->hardware = CS4231_HW_CS4236B;
					break;
				default:
					snd_printk("unknown CS4236 chip (enhanced version = 0x%x)\n", id);
				}
			} else if ((id & 0x1f) == 0x08) {	/* CS4237B */
				chip->hardware = CS4231_HW_CS4237B;
				switch (id >> 5) {
				case 4:
				case 5:
				case 6:
				case 7:
					break;
				default:
					snd_printk("unknown CS4237B chip (enhanced version = 0x%x)\n", id);
				}
			} else if ((id & 0x1f) == 0x09) {	/* CS4238B */
				chip->hardware = CS4231_HW_CS4238B;
				switch (id >> 5) {
				case 5:
				case 6:
				case 7:
					break;
				default:
					snd_printk("unknown CS4238B chip (enhanced version = 0x%x)\n", id);
				}
			} else if ((id & 0x1f) == 0x1e) {	/* CS4239 */
				chip->hardware = CS4231_HW_CS4239;
				switch (id >> 5) {
				case 4:
				case 5:
				case 6:
					break;
				default:
					snd_printk("unknown CS4239 chip (enhanced version = 0x%x)\n", id);
				}
			} else {
				snd_printk("unknown CS4236/CS423xB chip (enhanced version = 0x%x)\n", id);
			}
		}
	}
	return 0;		/* all things are ok.. */
}

/*

 */

static struct snd_pcm_hardware snd_cs4231_playback =
{
	.info =			(SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
				 SNDRV_PCM_INFO_MMAP_VALID |
				 SNDRV_PCM_INFO_RESUME |
				 SNDRV_PCM_INFO_SYNC_START),
	.formats =		(SNDRV_PCM_FMTBIT_MU_LAW | SNDRV_PCM_FMTBIT_A_LAW | SNDRV_PCM_FMTBIT_IMA_ADPCM |
				 SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S16_BE),
	.rates =		SNDRV_PCM_RATE_KNOT | SNDRV_PCM_RATE_8000_48000,
	.rate_min =		5510,
	.rate_max =		48000,
	.channels_min =		1,
	.channels_max =		2,
	.buffer_bytes_max =	(128*1024),
	.period_bytes_min =	64,
	.period_bytes_max =	(128*1024),
	.periods_min =		1,
	.periods_max =		1024,
	.fifo_size =		0,
};

static struct snd_pcm_hardware snd_cs4231_capture =
{
	.info =			(SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
				 SNDRV_PCM_INFO_MMAP_VALID |
				 SNDRV_PCM_INFO_RESUME |
				 SNDRV_PCM_INFO_SYNC_START),
	.formats =		(SNDRV_PCM_FMTBIT_MU_LAW | SNDRV_PCM_FMTBIT_A_LAW | SNDRV_PCM_FMTBIT_IMA_ADPCM |
				 SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S16_BE),
	.rates =		SNDRV_PCM_RATE_KNOT | SNDRV_PCM_RATE_8000_48000,
	.rate_min =		5510,
	.rate_max =		48000,
	.channels_min =		1,
	.channels_max =		2,
	.buffer_bytes_max =	(128*1024),
	.period_bytes_min =	64,
	.period_bytes_max =	(128*1024),
	.periods_min =		1,
	.periods_max =		1024,
	.fifo_size =		0,
};

/*

 */

static int snd_cs4231_playback_open(struct snd_pcm_substream *substream)
{
	struct snd_cs4231 *chip = snd_pcm_substream_chip(substream);
	struct snd_pcm_runtime *runtime = substream->runtime;
	int err;

	runtime->hw = snd_cs4231_playback;

	/* hardware bug in InterWave chipset */
	if (chip->hardware == CS4231_HW_INTERWAVE && chip->dma1 > 3)
	    	runtime->hw.formats &= ~SNDRV_PCM_FMTBIT_MU_LAW;
	
	/* hardware limitation of cheap chips */
	if (chip->hardware == CS4231_HW_CS4235 ||
	    chip->hardware == CS4231_HW_CS4239)
		runtime->hw.formats = SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE;

	snd_pcm_limit_isa_dma_size(chip->dma1, &runtime->hw.buffer_bytes_max);
	snd_pcm_limit_isa_dma_size(chip->dma1, &runtime->hw.period_bytes_max);

	if (chip->claim_dma) {
		if ((err = chip->claim_dma(chip, chip->dma_private_data, chip->dma1)) < 0)
			return err;
	}

	if ((err = snd_cs4231_open(chip, CS4231_MODE_PLAY)) < 0) {
		if (chip->release_dma)
			chip->release_dma(chip, chip->dma_private_data, chip->dma1);
		snd_free_pages(runtime->dma_area, runtime->dma_bytes);
		return err;
	}
	chip->playback_substream = substream;
	snd_pcm_set_sync(substream);
	chip->rate_constraint(runtime);
	return 0;
}

static int snd_cs4231_capture_open(struct snd_pcm_substream *substream)
{
	struct snd_cs4231 *chip = snd_pcm_substream_chip(substream);
	struct snd_pcm_runtime *runtime = substream->runtime;
	int err;

	runtime->hw = snd_cs4231_capture;

	/* hardware limitation of cheap chips */
	if (chip->hardware == CS4231_HW_CS4235 ||
	    chip->hardware == CS4231_HW_CS4239)
		runtime->hw.formats = SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE;

	snd_pcm_limit_isa_dma_size(chip->dma2, &runtime->hw.buffer_bytes_max);
	snd_pcm_limit_isa_dma_size(chip->dma2, &runtime->hw.period_bytes_max);

	if (chip->claim_dma) {
		if ((err = chip->claim_dma(chip, chip->dma_private_data, chip->dma2)) < 0)
			return err;
	}

	if ((err = snd_cs4231_open(chip, CS4231_MODE_RECORD)) < 0) {
		if (chip->release_dma)
			chip->release_dma(chip, chip->dma_private_data, chip->dma2);
		snd_free_pages(runtime->dma_area, runtime->dma_bytes);
		return err;
	}
	chip->capture_substream = substream;
	snd_pcm_set_sync(substream);
	chip->rate_constraint(runtime);
	return 0;
}

static int snd_cs4231_playback_close(struct snd_pcm_substream *substream)
{
	struct snd_cs4231 *chip = snd_pcm_substream_chip(substream);

	chip->playback_substream = NULL;
	snd_cs4231_close(chip, CS4231_MODE_PLAY);
	return 0;
}

static int snd_cs4231_capture_close(struct snd_pcm_substream *substream)
{
	struct snd_cs4231 *chip = snd_pcm_substream_chip(substream);

	chip->capture_substream = NULL;
	snd_cs4231_close(chip, CS4231_MODE_RECORD);
	return 0;
}

#ifdef CONFIG_PM

/* lowlevel suspend callback for CS4231 */
static void snd_cs4231_suspend(struct snd_cs4231 *chip)
{
	int reg;
	unsigned long flags;
	
	snd_pcm_suspend_all(chip->pcm);
	spin_lock_irqsave(&chip->reg_lock, flags);
	for (reg = 0; reg < 32; reg++)
		chip->image[reg] = snd_cs4231_in(chip, reg);
	spin_unlock_irqrestore(&chip->reg_lock, flags);
}

/* lowlevel resume callback for CS4231 */
static void snd_cs4231_resume(struct snd_cs4231 *chip)
{
	int reg;
	unsigned long flags;
	/* int timeout; */
	
	snd_cs4231_mce_up(chip);
	spin_lock_irqsave(&chip->reg_lock, flags);
	for (reg = 0; reg < 32; reg++) {
		switch (reg) {
		case CS4231_VERSION:
			break;
		default:
			snd_cs4231_out(chip, reg, chip->image[reg]);
			break;
		}
	}
	spin_unlock_irqrestore(&chip->reg_lock, flags);
#if 1
	snd_cs4231_mce_down(chip);
#else
	/* The following is a workaround to avoid freeze after resume on TP600E.
	   This is the first half of copy of snd_cs4231_mce_down(), but doesn't
	   include rescheduling.  -- iwai
	   */
	snd_cs4231_busy_wait(chip);
	spin_lock_irqsave(&chip->reg_lock, flags);
	chip->mce_bit &= ~CS4231_MCE;
	timeout = cs4231_inb(chip, CS4231P(REGSEL));
	cs4231_outb(chip, CS4231P(REGSEL), chip->mce_bit | (timeout & 0x1f));
	spin_unlock_irqrestore(&chip->reg_lock, flags);
	if (timeout == 0x80)
		snd_printk("down [0x%lx]: serious init problem - codec still busy\n", chip->port);
	if ((timeout & CS4231_MCE) == 0 ||
	    !(chip->hardware & (CS4231_HW_CS4231_MASK | CS4231_HW_CS4232_MASK))) {
		return;
	}
	snd_cs4231_busy_wait(chip);
#endif
}
#endif /* CONFIG_PM */

static int snd_cs4231_free(struct snd_cs4231 *chip)
{
	release_and_free_resource(chip->res_port);
	release_and_free_resource(chip->res_cport);
	if (chip->irq >= 0) {
		disable_irq(chip->irq);
		if (!(chip->hwshare & CS4231_HWSHARE_IRQ))
			free_irq(chip->irq, (void *) chip);
	}
	if (!(chip->hwshare & CS4231_HWSHARE_DMA1) && chip->dma1 >= 0) {
		snd_dma_disable(chip->dma1);
		free_dma(chip->dma1);
	}
	if (!(chip->hwshare & CS4231_HWSHARE_DMA2) && chip->dma2 >= 0 && chip->dma2 != chip->dma1) {
		snd_dma_disable(chip->dma2);
		free_dma(chip->dma2);
	}
	if (chip->timer)
		snd_device_free(chip->card, chip->timer);
	kfree(chip);
	return 0;
}

static int snd_cs4231_dev_free(struct snd_device *device)
{
	struct snd_cs4231 *chip = device->device_data;
	return snd_cs4231_free(chip);	
}

const char *snd_cs4231_chip_id(struct snd_cs4231 *chip)
{
	switch (chip->hardware) {
	case CS4231_HW_CS4231:	return "CS4231";
	case CS4231_HW_CS4231A: return "CS4231A";
	case CS4231_HW_CS4232:	return "CS4232";
	case CS4231_HW_CS4232A:	return "CS4232A";
	case CS4231_HW_CS4235:	return "CS4235";
	case CS4231_HW_CS4236:  return "CS4236";
	case CS4231_HW_CS4236B: return "CS4236B";
	case CS4231_HW_CS4237B: return "CS4237B";
	case CS4231_HW_CS4238B: return "CS4238B";
	case CS4231_HW_CS4239:	return "CS4239";
	case CS4231_HW_INTERWAVE: return "AMD InterWave";
	case CS4231_HW_OPL3SA2: return chip->card->shortname;
	case CS4231_HW_AD1845: return "AD1845";
	default: return "???";
	}
}

static int snd_cs4231_new(struct snd_card *card,
			  unsigned short hardware,
			  unsigned short hwshare,
			  struct snd_cs4231 ** rchip)
{
	struct snd_cs4231 *chip;

	*rchip = NULL;
	chip = kzalloc(sizeof(*chip), GFP_KERNEL);
	if (chip == NULL)
		return -ENOMEM;
	chip->hardware = hardware;
	chip->hwshare = hwshare;

	spin_lock_init(&chip->reg_lock);
	mutex_init(&chip->mce_mutex);
	mutex_init(&chip->open_mutex);
	chip->card = card;
	chip->rate_constraint = snd_cs4231_xrate;
	chip->set_playback_format = snd_cs4231_playback_format;
	chip->set_capture_format = snd_cs4231_capture_format;
        memcpy(&chip->image, &snd_cs4231_original_image, sizeof(snd_cs4231_original_image));
        
        *rchip = chip;
        return 0;
}

int snd_cs4231_create(struct snd_card *card,
	              unsigned long port,
	              unsigned long cport,
		      int irq, int dma1, int dma2,
		      unsigned short hardware,
		      unsigned short hwshare,
		      struct snd_cs4231 ** rchip)
{
	static struct snd_device_ops ops = {
		.dev_free =	snd_cs4231_dev_free,
	};
	struct snd_cs4231 *chip;
	int err;

	err = snd_cs4231_new(card, hardware, hwshare, &chip);
	if (err < 0)
		return err;
	
	chip->irq = -1;
	chip->dma1 = -1;
	chip->dma2 = -1;

	if ((chip->res_port = request_region(port, 4, "CS4231")) == NULL) {
		snd_printk(KERN_ERR "cs4231: can't grab port 0x%lx\n", port);
		snd_cs4231_free(chip);
		return -EBUSY;
	}
	chip->port = port;
	if ((long)cport >= 0 && (chip->res_cport = request_region(cport, 8, "CS4232 Control")) == NULL) {
		snd_printk(KERN_ERR "cs4231: can't grab control port 0x%lx\n", cport);
		snd_cs4231_free(chip);
		return -ENODEV;
	}
	chip->cport = cport;
	if (!(hwshare & CS4231_HWSHARE_IRQ) && request_irq(irq, snd_cs4231_interrupt, IRQF_DISABLED, "CS4231", (void *) chip)) {
		snd_printk(KERN_ERR "cs4231: can't grab IRQ %d\n", irq);
		snd_cs4231_free(chip);
		return -EBUSY;
	}
	chip->irq = irq;
	if (!(hwshare & CS4231_HWSHARE_DMA1) && request_dma(dma1, "CS4231 - 1")) {
		snd_printk(KERN_ERR "cs4231: can't grab DMA1 %d\n", dma1);
		snd_cs4231_free(chip);
		return -EBUSY;
	}
	chip->dma1 = dma1;
	if (!(hwshare & CS4231_HWSHARE_DMA2) && dma1 != dma2 && dma2 >= 0 && request_dma(dma2, "CS4231 - 2")) {
		snd_printk(KERN_ERR "cs4231: can't grab DMA2 %d\n", dma2);
		snd_cs4231_free(chip);
		return -EBUSY;
	}
	if (dma1 == dma2 || dma2 < 0) {
		chip->single_dma = 1;
		chip->dma2 = chip->dma1;
	} else
		chip->dma2 = dma2;

	/* global setup */
	if (snd_cs4231_probe(chip) < 0) {
		snd_cs4231_free(chip);
		return -ENODEV;
	}
	snd_cs4231_init(chip);

#if 0
	if (chip->hardware & CS4231_HW_CS4232_MASK) {
		if (chip->res_cport == NULL)
			snd_printk("CS4232 control port features are not accessible\n");
	}
#endif

	/* Register device */
	if ((err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops)) < 0) {
		snd_cs4231_free(chip);
		return err;
	}

#ifdef CONFIG_PM
	/* Power Management */
	chip->suspend = snd_cs4231_suspend;
	chip->resume = snd_cs4231_resume;
#endif

	*rchip = chip;
	return 0;
}

static struct snd_pcm_ops snd_cs4231_playback_ops = {
	.open =		snd_cs4231_playback_open,
	.close =	snd_cs4231_playback_close,
	.ioctl =	snd_pcm_lib_ioctl,
	.hw_params =	snd_cs4231_playback_hw_params,
	.hw_free =	snd_cs4231_playback_hw_free,
	.prepare =	snd_cs4231_playback_prepare,
	.trigger =	snd_cs4231_trigger,
	.pointer =	snd_cs4231_playback_pointer,
};

static struct snd_pcm_ops snd_cs4231_capture_ops = {
	.open =		snd_cs4231_capture_open,
	.close =	snd_cs4231_capture_close,
	.ioctl =	snd_pcm_lib_ioctl,
	.hw_params =	snd_cs4231_capture_hw_params,
	.hw_free =	snd_cs4231_capture_hw_free,
	.prepare =	snd_cs4231_capture_prepare,
	.trigger =	snd_cs4231_trigger,
	.pointer =	snd_cs4231_capture_pointer,
};

int snd_cs4231_pcm(struct snd_cs4231 *chip, int device, struct snd_pcm **rpcm)
{
	struct snd_pcm *pcm;
	int err;

	if ((err = snd_pcm_new(chip->card, "CS4231", device, 1, 1, &pcm)) < 0)
		return err;

	spin_lock_init(&chip->reg_lock);
	mutex_init(&chip->mce_mutex);
	mutex_init(&chip->open_mutex);

	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_cs4231_playback_ops);
	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_cs4231_capture_ops);
	
	/* global setup */
	pcm->private_data = chip;
	pcm->info_flags = 0;
	if (chip->single_dma)
		pcm->info_flags |= SNDRV_PCM_INFO_HALF_DUPLEX;
	if (chip->hardware != CS4231_HW_INTERWAVE)
		pcm->info_flags |= SNDRV_PCM_INFO_JOINT_DUPLEX;
	strcpy(pcm->name, snd_cs4231_chip_id(chip));

	snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV,
					      snd_dma_isa_data(),
					      64*1024, chip->dma1 > 3 || chip->dma2 > 3 ? 128*1024 : 64*1024);

	chip->pcm = pcm;
	if (rpcm)
		*rpcm = pcm;
	return 0;
}

static void snd_cs4231_timer_free(struct snd_timer *timer)
{
	struct snd_cs4231 *chip = timer->private_data;
	chip->timer = NULL;
}

int snd_cs4231_timer(struct snd_cs4231 *chip, int device, struct snd_timer **rtimer)
{
	struct snd_timer *timer;
	struct snd_timer_id tid;
	int err;

	/* Timer initialization */
	tid.dev_class = SNDRV_TIMER_CLASS_CARD;
	tid.dev_sclass = SNDRV_TIMER_SCLASS_NONE;
	tid.card = chip->card->number;
	tid.device = device;
	tid.subdevice = 0;
	if ((err = snd_timer_new(chip->card, "CS4231", &tid, &timer)) < 0)
		return err;
	strcpy(timer->name, snd_cs4231_chip_id(chip));
	timer->private_data = chip;
	timer->private_free = snd_cs4231_timer_free;
	timer->hw = snd_cs4231_timer_table;
	chip->timer = timer;
	if (rtimer)
		*rtimer = timer;
	return 0;
}
	
/*
 *  MIXER part
 */

static int snd_cs4231_info_mux(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
{
	static char *texts[4] = {
		"Line", "Aux", "Mic", "Mix"
	};
	static char *opl3sa_texts[4] = {
		"Line", "CD", "Mic", "Mix"
	};
	static char *gusmax_texts[4] = {
		"Line", "Synth", "Mic", "Mix"
	};
	char **ptexts = texts;
	struct snd_cs4231 *chip = snd_kcontrol_chip(kcontrol);

	snd_assert(chip->card != NULL, return -EINVAL);
	uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
	uinfo->count = 2;
	uinfo->value.enumerated.items = 4;
	if (uinfo->value.enumerated.item > 3)
		uinfo->value.enumerated.item = 3;
	if (!strcmp(chip->card->driver, "GUS MAX"))
		ptexts = gusmax_texts;
	switch (chip->hardware) {
	case CS4231_HW_INTERWAVE: ptexts = gusmax_texts; break;
	case CS4231_HW_OPL3SA2: ptexts = opl3sa_texts; break;
	}
	strcpy(uinfo->value.enumerated.name, ptexts[uinfo->value.enumerated.item]);
	return 0;
}

static int snd_cs4231_get_mux(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
{
	struct snd_cs4231 *chip = snd_kcontrol_chip(kcontrol);
	unsigned long flags;
	
	spin_lock_irqsave(&chip->reg_lock, flags);
	ucontrol->value.enumerated.item[0] = (chip->image[CS4231_LEFT_INPUT] & CS4231_MIXS_ALL) >> 6;
	ucontrol->value.enumerated.item[1] = (chip->image[CS4231_RIGHT_INPUT] & CS4231_MIXS_ALL) >> 6;
	spin_unlock_irqrestore(&chip->reg_lock, flags);
	return 0;
}

static int snd_cs4231_put_mux(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
{
	struct snd_cs4231 *chip = snd_kcontrol_chip(kcontrol);
	unsigned long flags;
	unsigned short left, right;
	int change;
	
	if (ucontrol->value.enumerated.item[0] > 3 ||
	    ucontrol->value.enumerated.item[1] > 3)
		return -EINVAL;
	left = ucontrol->value.enumerated.item[0] << 6;
	right = ucontrol->value.enumerated.item[1] << 6;
	spin_lock_irqsave(&chip->reg_lock, flags);
	left = (chip->image[CS4231_LEFT_INPUT] & ~CS4231_MIXS_ALL) | left;
	right = (chip->image[CS4231_RIGHT_INPUT] & ~CS4231_MIXS_ALL) | right;
	change = left != chip->image[CS4231_LEFT_INPUT] ||
	         right != chip->image[CS4231_RIGHT_INPUT];
	snd_cs4231_out(chip, CS4231_LEFT_INPUT, left);
	snd_cs4231_out(chip, CS4231_RIGHT_INPUT, right);
	spin_unlock_irqrestore(&chip->reg_lock, flags);
	return change;
}

int snd_cs4231_info_single(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *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;
}

int snd_cs4231_get_single(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
{
	struct snd_cs4231 *chip = snd_kcontrol_chip(kcontrol);
	unsigned long flags;
	int reg = kcontrol->private_value & 0xff;
	int shift = (kcontrol->private_value >> 8) & 0xff;
	int mask = (kcontrol->private_value >> 16) & 0xff;
	int invert = (kcontrol->private_value >> 24) & 0xff;
	
	spin_lock_irqsave(&chip->reg_lock, flags);
	ucontrol->value.integer.value[0] = (chip->image[reg] >> shift) & mask;
	spin_unlock_irqrestore(&chip->reg_lock, flags);
	if (invert)
		ucontrol->value.integer.value[0] = mask - ucontrol->value.integer.value[0];
	return 0;
}

int snd_cs4231_put_single(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
{
	struct snd_cs4231 *chip = snd_kcontrol_chip(kcontrol);
	unsigned long flags;
	int reg = kcontrol->private_value & 0xff;
	int shift = (kcontrol->private_value >> 8) & 0xff;
	int mask = (kcontrol->private_value >> 16) & 0xff;
	int invert = (kcontrol->private_value >> 24) & 0xff;
	int change;
	unsigned short val;
	
	val = (ucontrol->value.integer.value[0] & mask);
	if (invert)
		val = mask - val;
	val <<= shift;
	spin_lock_irqsave(&chip->reg_lock, flags);
	val = (chip->image[reg] & ~(mask << shift)) | val;
	change = val != chip->image[reg];
	snd_cs4231_out(chip, reg, val);
	spin_unlock_irqrestore(&chip->reg_lock, flags);
	return change;
}

int snd_cs4231_info_double(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
{
	int mask = (kcontrol->private_value >> 24) & 0xff;

	uinfo->type = mask == 1 ? SNDRV_CTL_ELEM_TYPE_BOOLEAN : SNDRV_CTL_ELEM_TYPE_INTEGER;
	uinfo->count = 2;
	uinfo->value.integer.min = 0;
	uinfo->value.integer.max = mask;
	return 0;
}

int snd_cs4231_get_double(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
{
	struct snd_cs4231 *chip = snd_kcontrol_chip(kcontrol);
	unsigned long flags;
	int left_reg = kcontrol->private_value & 0xff;
	int right_reg = (kcontrol->private_value >> 8) & 0xff;
	int shift_left = (kcontrol->private_value >> 16) & 0x07;
	int shift_right = (kcontrol->private_value >> 19) & 0x07;
	int mask = (kcontrol->private_value >> 24) & 0xff;
	int invert = (kcontrol->private_value >> 22) & 1;
	
	spin_lock_irqsave(&chip->reg_lock, flags);
	ucontrol->value.integer.value[0] = (chip->image[left_reg] >> shift_left) & mask;
	ucontrol->value.integer.value[1] = (chip->image[right_reg] >> shift_right) & mask;
	spin_unlock_irqrestore(&chip->reg_lock, flags);
	if (invert) {
		ucontrol->value.integer.value[0] = mask - ucontrol->value.integer.value[0];
		ucontrol->value.integer.value[1] = mask - ucontrol->value.integer.value[1];
	}
	return 0;
}

int snd_cs4231_put_double(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
{
	struct snd_cs4231 *chip = snd_kcontrol_chip(kcontrol);
	unsigned long flags;
	int left_reg = kcontrol->private_value & 0xff;
	int right_reg = (kcontrol->private_value >> 8) & 0xff;
	int shift_left = (kcontrol->private_value >> 16) & 0x07;
	int shift_right = (kcontrol->private_value >> 19) & 0x07;
	int mask = (kcontrol->private_value >> 24) & 0xff;
	int invert = (kcontrol->private_value >> 22) & 1;
	int change;
	unsigned short val1, val2;
	
	val1 = ucontrol->value.integer.value[0] & mask;
	val2 = ucontrol->value.integer.value[1] & mask;
	if (invert) {
		val1 = mask - val1;
		val2 = mask - val2;
	}
	val1 <<= shift_left;
	val2 <<= shift_right;
	spin_lock_irqsave(&chip->reg_lock, flags);
	val1 = (chip->image[left_reg] & ~(mask << shift_left)) | val1;
	val2 = (chip->image[right_reg] & ~(mask << shift_right)) | val2;
	change = val1 != chip->image[left_reg] || val2 != chip->image[right_reg];
	snd_cs4231_out(chip, left_reg, val1);
	snd_cs4231_out(chip, right_reg, val2);
	spin_unlock_irqrestore(&chip->reg_lock, flags);
	return change;
}

static struct snd_kcontrol_new snd_cs4231_controls[] = {
CS4231_DOUBLE("PCM Playback Switch", 0, CS4231_LEFT_OUTPUT, CS4231_RIGHT_OUTPUT, 7, 7, 1, 1),
CS4231_DOUBLE("PCM Playback Volume", 0, CS4231_LEFT_OUTPUT, CS4231_RIGHT_OUTPUT, 0, 0, 63, 1),
CS4231_DOUBLE("Line Playback Switch", 0, CS4231_LEFT_LINE_IN, CS4231_RIGHT_LINE_IN, 7, 7, 1, 1),
CS4231_DOUBLE("Line Playback Volume", 0, CS4231_LEFT_LINE_IN, CS4231_RIGHT_LINE_IN, 0, 0, 31, 1),
CS4231_DOUBLE("Aux Playback Switch", 0, CS4231_AUX1_LEFT_INPUT, CS4231_AUX1_RIGHT_INPUT, 7, 7, 1, 1),
CS4231_DOUBLE("Aux Playback Volume", 0, CS4231_AUX1_LEFT_INPUT, CS4231_AUX1_RIGHT_INPUT, 0, 0, 31, 1),
CS4231_DOUBLE("Aux Playback Switch", 1, CS4231_AUX2_LEFT_INPUT, CS4231_AUX2_RIGHT_INPUT, 7, 7, 1, 1),
CS4231_DOUBLE("Aux Playback Volume", 1, CS4231_AUX2_LEFT_INPUT, CS4231_AUX2_RIGHT_INPUT, 0, 0, 31, 1),
CS4231_SINGLE("Mono Playback Switch", 0, CS4231_MONO_CTRL, 7, 1, 1),
CS4231_SINGLE("Mono Playback Volume", 0, CS4231_MONO_CTRL, 0, 15, 1),
CS4231_SINGLE("Mono Output Playback Switch", 0, CS4231_MONO_CTRL, 6, 1, 1),
CS4231_SINGLE("Mono Output Playback Bypass", 0, CS4231_MONO_CTRL, 5, 1, 0),
CS4231_DOUBLE("Capture Volume", 0, CS4231_LEFT_INPUT, CS4231_RIGHT_INPUT, 0, 0, 15, 0),
{
	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
	.name = "Capture Source",
	.info = snd_cs4231_info_mux,
	.get = snd_cs4231_get_mux,
	.put = snd_cs4231_put_mux,
},
CS4231_DOUBLE("Mic Boost", 0, CS4231_LEFT_INPUT, CS4231_RIGHT_INPUT, 5, 5, 1, 0),
CS4231_SINGLE("Loopback Capture Switch", 0, CS4231_LOOPBACK, 0, 1, 0),
CS4231_SINGLE("Loopback Capture Volume", 0, CS4231_LOOPBACK, 2, 63, 1)
};
                                        
int snd_cs4231_mixer(struct snd_cs4231 *chip)
{
	struct snd_card *card;
	unsigned int idx;
	int err;

	snd_assert(chip != NULL && chip->pcm != NULL, return -EINVAL);

	card = chip->card;

	strcpy(card->mixername, chip->pcm->name);

	for (idx = 0; idx < ARRAY_SIZE(snd_cs4231_controls); idx++) {
		if ((err = snd_ctl_add(card, snd_ctl_new1(&snd_cs4231_controls[idx], chip))) < 0)
			return err;
	}
	return 0;
}

EXPORT_SYMBOL(snd_cs4231_out);
EXPORT_SYMBOL(snd_cs4231_in);
EXPORT_SYMBOL(snd_cs4236_ext_out);
EXPORT_SYMBOL(snd_cs4236_ext_in);
EXPORT_SYMBOL(snd_cs4231_mce_up);
EXPORT_SYMBOL(snd_cs4231_mce_down);
EXPORT_SYMBOL(snd_cs4231_interrupt);
EXPORT_SYMBOL(snd_cs4231_chip_id);
EXPORT_SYMBOL(snd_cs4231_create);
EXPORT_SYMBOL(snd_cs4231_pcm);
EXPORT_SYMBOL(snd_cs4231_mixer);
EXPORT_SYMBOL(snd_cs4231_timer);
EXPORT_SYMBOL(snd_cs4231_info_single);
EXPORT_SYMBOL(snd_cs4231_get_single);
EXPORT_SYMBOL(snd_cs4231_put_single);
EXPORT_SYMBOL(snd_cs4231_info_double);
EXPORT_SYMBOL(snd_cs4231_get_double);
EXPORT_SYMBOL(snd_cs4231_put_double);

/*
 *  INIT part
 */

static int __init alsa_cs4231_init(void)
{
	return 0;
}

static void __exit alsa_cs4231_exit(void)
{
}

module_init(alsa_cs4231_init)
module_exit(alsa_cs4231_exit)