Hi, This is a work-in-progress patch for supporting the Si3055 HDAudio softmodem.
It can already play and record sound from the phone line. I could successfully dial to a phone number using a DTMF generator and talk to people in the telephone by piping ossrecord to ossplay and vice-versa. I'm very grateful to Yair (cesium) and François (mmu_man), who gave a lot of advices by IRC. When concluded, this code will allow for cross-platform softmodem support. I already ported slmodem and linmodem for using OSSv4, and François already ported linmodem for BeOS/Haiku. So slmodem will be usable in all our x86 (because it contains some closed-source object code) supported OSs, and linmodem (fully opensource) will be usable in all supported platforms (although linmodem currently only supports slowish connection speeds). An "osstelephone" tool can easily be created too, to allow for easily using the computer as a telephone. However, this is just a start, and the code is extremely disorganized. So I'm writing to ask for advice about how to improve this patch. Some issues remaining: 1) We need a new ioctl for going Off-Hook (connected to the phone line) and On-Hook (disconnected from the phone line). We discussed this in IRC and initially we though about going off-hook/on-hook when opening/closing the device, because we didn't see any sense in recording sound from an on-hook modem. However, ALSA uses a mixer control for off-hook, and NetBSD (http://mail-index.netbsd.org/current-users/2005/04/07/0022.html) uses a sysctl for it. Later, we found a reason - when pulse dialing, someone needs to receive sound from the modem while it is on-hook, to know when the line tone ceases. Indeed, slmodem does that. So we need an ioctl for going Off-Hook or On-Hook. In the patch, I proposed the name SNDCTL_MODEM_OFFHOOK, but better names suggestions are welcome. It could be implemented in hdaudio_codec_audio_ioctl(), but then another code organization issue would arise. How to implement it without disorganizing the code even more? 2) Perhaps DUPLEX devices for modems would be better. By the following reasons: - linmodem and slmodem have code designed so that it's more adequate to use duplex devices. - Two different device nodes for a modem looks somewhat cumbersome, and has strange semantics (for example for off-hook). - The modem has only one sampling rate register, for both playback and record. Let's suppose you open the recording device at 16kHz, then forget the DTMF generator uses 8kHz and call it in the playback device. When it sets the playback device sampling rate, the modem will change the recoding sampling rate too, and nobody will know about that, so messing up the recording stream. This actually happened to me when I was testing the code. Using duplex devices would avoid that. François suggested the device nodes being named "mdm%d". It looks nice. Perhaps install_outputdevs() would need to be modified for using duplex for modems, and hdaudio_endpointinfo_t would need to be modified for being suitable for that. 3) Again, code organization. How to organize this code so it doesn't look so hackish? 4) The modifications to allow mono devices in install_outputdevs() were hackish. Maybe they could be cleaned up by solving items 2 and 3. A modification in hda_audio_set_channels() was needed because it was forcing at least 2 channels. Is it OK? 5) Solving stream_number issues. Perhaps OSS treats the stream numbers as something that needs to be unique in the scope of a codec. For example, in attach_node(), we have: endpoint->stream_number = endpoint->default_stream_number = codec->num_inendpoints; So, this way, the OSS lets identical stream_numbers repeat in different codecs. However, by reading the HDAudio specification, at page 17, the figure shows that stream_numbers should be treated in the Azalia controller scope. That is, a stream_number needs to be unique inside an entire controller, over all of its codecs. In fact, if I assign stream_number=0 to the modem engine, sound actually plays in the speaker instead of the phone line. I temporarily solved this by using the maximum permitted stream_number (0xf), but this is very hackish. Perhaps we need a stream counter in some controller-wise struct, like hda_devc_t or even hdaudio_mixer_t. Also, can I use the same stream number for the input and output streams? It's not much clear from the specifications, but appears to work, and OSS appears to already do this in attach_node(). Some random stuff related to this softmodem work can be found here: http://basalto.ifsc.usp.br/~paulo_matias/modem/ Please help, I still need a lot of advice on this! Thanks and best regards, Paulo Matias
diff -r 7656ac428350 include/soundcard.h --- a/include/soundcard.h Wed Oct 29 22:46:59 2008 +0200 +++ b/include/soundcard.h Tue Nov 04 23:07:48 2008 -0200 @@ -2010,6 +2010,8 @@ #define SNDCTL_MIX_DESCRIPTION __SIOWR('X',14, oss_mixer_enuminfo) +#define SNDCTL_MODEM_OFFHOOK __SIOWR('X',15, int) + /* ioctl codes 'X', 200-255 are reserved for internal use */ /* diff -r 7656ac428350 kernel/drv/oss_hdaudio/hdaudio_codec.c --- a/kernel/drv/oss_hdaudio/hdaudio_codec.c Wed Oct 29 22:46:59 2008 +0200 +++ b/kernel/drv/oss_hdaudio/hdaudio_codec.c Tue Nov 04 23:07:49 2008 -0200 @@ -7,6 +7,26 @@ #include "hdaudio.h" #include "hdaudio_codec.h" #include "hdaudio_codecids.h" + + + +/* Si3055 verbs. */ +#define SI3055_REG_GET_VERB 0x900 +#define SI3055_REG_SET_VERB 0x100 + +/* Convenience macros for reading from and writing to Si3055 registers. */ +#define SI3055_REG_GET(mixer, cad, reg, a, b) corb_read(mixer, cad, reg, 0, SI3055_REG_GET_VERB, 0, a, b) +#define SI3055_REG_SET(mixer, cad, reg, val) corb_write(mixer, cad, reg, 0, SI3055_REG_SET_VERB, val) +/* + * Si3055 register IDs. + */ +#define SI3055_EXT_MODEM_STATUS 2 +#define SI3055_LINE_RATE 3 +#define SI3055_HDA_STREAMS 4 +#define SI3055_GPIO_PIN_STATUS 10 +#define SI3055_LINE_CONFIG 13 + + extern int hdaudio_snoopy; extern int hdaudio_jacksense; @@ -1909,6 +1929,8 @@ } } +extern int hdaudio_si3055_endpoint_init(hdaudio_mixer_t * mixer, int cad); + /* ARGSUSED */ static int attach_codec (hdaudio_mixer_t * mixer, int cad, char *hw_info, @@ -2128,6 +2150,11 @@ /* power up the AFG! */ corb_write (mixer, cad, i, 0, SET_POWER_STATE, 0); } + + /* Initialize and setup manually endpoints for Si3055. */ + if ((mixer->codecs[cad]->vendor_flags & VF_SI3055_HACK) && (group_type == 2)) { + hdaudio_si3055_endpoint_init(mixer, cad); + } if (has_audio_group) { @@ -2258,6 +2285,16 @@ } *setupbits = tmp; + + if (mixer->codecs[endpoint->cad]->vendor_flags & VF_SI3055_HACK) + { + unsigned int a, b; + cmn_err(CE_CONT, "hdaudio_codec_setup_endpoint for Si3055.\n"); + cmn_err(CE_CONT, "endpoint dbg: %d, %d, %d, %d.\n", rate, channels, fmt, stream_number); + SI3055_REG_SET(mixer, endpoint->cad, SI3055_LINE_RATE, rate); + SI3055_REG_GET(mixer, endpoint->cad, SI3055_LINE_RATE, &a, &b); + cmn_err(CE_CONT, "new rate: %d\n", a); + } corb_write (mixer, endpoint->cad, endpoint->base_wid, 0, SET_CONVERTER_FORMAT, tmp); diff -r 7656ac428350 kernel/drv/oss_hdaudio/hdaudio_codec.h --- a/kernel/drv/oss_hdaudio/hdaudio_codec.h Wed Oct 29 22:46:59 2008 +0200 +++ b/kernel/drv/oss_hdaudio/hdaudio_codec.h Tue Nov 04 23:07:49 2008 -0200 @@ -127,6 +127,7 @@ #define SET_GPIO_STICKY 0x71a #define GET_SUBSYSTEM_ID 0xf20 #define GET_STRIPE_CONTROL 0xf24 +#define SET_CODEC_RESET 0x7ff /* * Parameters diff -r 7656ac428350 kernel/drv/oss_hdaudio/hdaudio_codecids.h --- a/kernel/drv/oss_hdaudio/hdaudio_codecids.h Wed Oct 29 22:46:59 2008 +0200 +++ b/kernel/drv/oss_hdaudio/hdaudio_codecids.h Tue Nov 04 23:07:49 2008 -0200 @@ -18,6 +18,8 @@ S/PDIF */ #define VF_VAIO_HACK 0x00000002 /* VAIO STAC9872 requires special handling for headphone DAC */ +#define VF_SI3055_HACK 0x00000004 /* Si3055 modem requires manual endpoint +setuping and rate and ioctl hacks. */ char **remap; /* @@ -815,6 +817,8 @@ NULL }; +extern int hdaudio_si3055_mixer_init (int dev, hdaudio_mixer_t * mixer, int cad, int top_group); + static const codec_desc_t codecs[] = { /* Realtek HDA codecs */ {0x10ec0260, "ALC260", VF_NONE, (char **) &alc260remap}, @@ -893,6 +897,16 @@ {0x14f15047, "CX20551", VF_NONE, NULL, 0x76543201}, {0x14f12c06, "Conexant2c06", VF_NONE, (char **) &conexant_modem_remap, 0, NULL_mixer_init}, /* Modem codec (Vaio) */ {0x14f12bfa, "Conexant2bfa", VF_NONE, (char **) &conexant_modem_remap, 0, NULL_mixer_init}, /* Modem codec (Acer Ferrari 5k) */ + + /* Si3055 and compatible modems */ + {0x163c3055, "Si3055", VF_SI3055_HACK, NULL, 0, hdaudio_si3055_mixer_init }, + {0x163c3155, "Si3155", VF_SI3055_HACK, NULL, 0, hdaudio_si3055_mixer_init }, + {0x11c13026, "Agere3026", VF_SI3055_HACK, NULL, 0, hdaudio_si3055_mixer_init }, + {0x11c13055, "Agere3055", VF_SI3055_HACK, NULL, 0, hdaudio_si3055_mixer_init }, + {0x11c13155, "Agere3155", VF_SI3055_HACK, NULL, 0, hdaudio_si3055_mixer_init }, + {0x10573055, "Motorola3055", VF_SI3055_HACK, NULL, 0, hdaudio_si3055_mixer_init }, + {0x10573057, "Motorola3057", VF_SI3055_HACK, NULL, 0, hdaudio_si3055_mixer_init }, + {0x10573155, "Motorola3155", VF_SI3055_HACK, NULL, 0, hdaudio_si3055_mixer_init }, /* Unknown */ {0, "Unknown"} diff -r 7656ac428350 kernel/drv/oss_hdaudio/hdaudio_si3055.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/kernel/drv/oss_hdaudio/hdaudio_si3055.c Tue Nov 04 23:07:49 2008 -0200 @@ -0,0 +1,192 @@ +/* + * Purpose: Driver for Si3055 and compatible modems. + */ +#define COPYING Copyright (C) 2008 Paulo Matias. Licensed to 4Front Technologies + + +/* + * Documentation Note + * + * There is no publicly available documentation for Si3055. However, + * there is a very similar modem (Si3038) for which a datasheet is + * publicly available: + * + * https://www.silabs.com/Support%20Documents/TechnicalDocs/si3038.pdf + * + * This driver was written by reading the ALSA code, looking for a + * similar modem (Si3038), and figuring out the corresponding Si3055 + * register IDs for Si3038 registers, again by reading the ALSA code, + * and by checking the default after-reset values. + * + */ + +#include "oss_hdaudio_cfg.h" +#include "hdaudio.h" +#include "hdaudio_codec.h" +#include "hdaudio_dedicated.h" +#include "hdaudio_mixers.h" + +/* + * Si3055 register IDs. + */ +#define SI3055_EXT_MODEM_STATUS 2 +#define SI3055_LINE_RATE 3 +#define SI3055_HDA_STREAMS 4 +#define SI3055_GPIO_CONFIG 5 +#define SI3055_GPIO_PIN_STATUS 10 +#define SI3055_LINE_CONFIG 13 + + +/* Corresponding registers in Si3038 (for reference): + * + * SI3055_EXT_MODEM_STATUS 3Eh (Extended Modem Status & Control) + * SI3055_LINE_RATE 40h (Line 1 DAC/ADC Rate) + * SI3055_GPIO_PIN_STATUS 54h (GPIO Pin Status) + * SI3055_LINE_CONFIG 56h (Line Side Configuration 1) + */ + +/* + * The SI3055_HDA_STREAMS register has no corresponding in Si3038. + * It contains the playback and recording stream descriptors in the + * following format: + * + * ((playback_stream_num << 4) << 8) | (recording_stream_num << 4) + */ + +/* Si3055 verbs. */ +#define SI3055_REG_GET_VERB 0x900 +#define SI3055_REG_SET_VERB 0x100 + +/* Convenience macros for reading from and writing to Si3055 registers. */ +#define SI3055_REG_GET(mixer, cad, reg, a, b) corb_read(mixer, cad, reg, 0, SI3055_REG_GET_VERB, 0, a, b) +#define SI3055_REG_SET(mixer, cad, reg, val) corb_write(mixer, cad, reg, 0, SI3055_REG_SET_VERB, val) + + +/* TODO: DDB() around debugging messages. */ + +int hdaudio_si3055_endpoint_init(hdaudio_mixer_t * mixer, int cad) +{ + codec_t *codec = mixer->codecs[cad]; /* Modem codec */ + widget_t *widget; /* MFG widget */ + hdaudio_endpointinfo_t *endpoint; + unsigned int a, b; /* Used for reading data. */ + int tmout; /* Timeout counter. */ + + cmn_err(CE_CONT, "hdaudio_si3055_endpoint_init got called.\n"); + + /* Reset the modem codec. */ + corb_write(mixer, cad, 0x00, 0, SET_CODEC_RESET, 0); + corb_write(mixer, cad, codec->afg, 0, SET_CONVERTER, IDDLE_STREAM << 4); + corb_write(mixer, cad, codec->afg, 0, SET_CONVERTER_FORMAT, 0); + + /* Set 9600Hz as the initial line sampling rate. + * It can be changed later when desired. + */ + SI3055_REG_SET(mixer, cad, SI3055_LINE_RATE, 9600); + + /* Assign the "unused" value to the playback and recording + * stream descriptors (ref. HDAudio_03.pdf, page 40). + */ + SI3055_REG_SET(mixer, cad, SI3055_HDA_STREAMS, 0x0000); + + /* Write 0x0000 to the Extended Modem Status & Control register + * to power up the modem (ref. si3038.pdf, page 22). + */ + SI3055_REG_SET(mixer, cad, SI3055_EXT_MODEM_STATUS, 0x0000); + + /* Wait for the modem to complete power up. The lower 8 bits + * indicate that it is ready (ref. si3038.pdf, page 22). + */ + tmout = 10; + do { + SI3055_REG_GET(mixer, cad, SI3055_EXT_MODEM_STATUS, &a, &b); + cmn_err(CE_CONT, "si3055: ext modem status: %04x.\n", a); + oss_udelay(1000); + } while(((a & 0xf) == 0) && --tmout); + + if((a & 0xf) == 0) { + cmn_err(CE_WARN, "Si3055 power up timeout (status: %04x).\n", a); + } + + /* This register contains 0x1fff after reset. We need to set it + * to zero to get the modem working. No corresponding register + * could be found in the Si3038 datasheet. + */ + SI3055_REG_SET(mixer, cad, SI3055_GPIO_CONFIG, 0x0000); + + /* Program line interface parameters. The register value after + * a reset is 0xF010. Set it to 0x0010 to unmute the analog + * receive and transmit paths. + */ + SI3055_REG_SET(mixer, cad, SI3055_LINE_CONFIG, 0x0010); + + /* Setup the stream numbers. */ + SI3055_REG_SET(mixer, cad, SI3055_HDA_STREAMS, 0xf0f0); + + /* TODO: We are always enabling Off-Hook, only for testing. + * This code needs to be moved to the ioctl later. + */ + SI3055_REG_GET(mixer, cad, SI3055_GPIO_PIN_STATUS, &a, &b); + cmn_err(CE_CONT, "si3055: gpio: %04x\n", a); + a |= 0x1; /* Set Off-Hook bit */ + SI3055_REG_SET(mixer, cad, SI3055_GPIO_PIN_STATUS, a); + SI3055_REG_GET(mixer, cad, SI3055_GPIO_PIN_STATUS, &a, &b); + cmn_err(CE_CONT, "si3055: gpio: %04x\n", a); + + /* Setup the widget info. */ + widget = &codec->widgets[codec->afg]; + widget->endpoint = &codec->inendpoints[0]; + widget->sizes = 0x20000; /* 16 bits */ + strcpy(widget->name, "modem"); + + /* Setup the output endpoint. */ + codec->num_outendpoints = 1; + endpoint = &codec->outendpoints[0]; + endpoint->stream_number = endpoint->default_stream_number = 0xf; + endpoint->ix = endpoint->stream_number - 1; + endpoint->iddle_stream = 0; + endpoint->cad = cad; + endpoint->base_wid = codec->afg; + endpoint->recsrc_wid = endpoint->volume_wid = -1; + endpoint->nrates = 3; + endpoint->rates[0] = 8000; + endpoint->rates[1] = 9600; + endpoint->rates[2] = 16000; + endpoint->name = widget->name; + endpoint->channels = 1; + endpoint->is_digital = 0; + endpoint->sizemask = widget->sizes; + endpoint->fmt_mask = AFMT_S16_LE; + endpoint->afg = codec->afg; + endpoint->min_rate = 8000; + endpoint->max_rate = 16000; + + /* Setup the input endpoint. */ + codec->num_inendpoints = 1; + endpoint = &codec->inendpoints[0]; + endpoint->stream_number = endpoint->default_stream_number = 0xf; + endpoint->ix = endpoint->stream_number - 1; + endpoint->iddle_stream = 0; + endpoint->cad = cad; + endpoint->base_wid = codec->afg; + endpoint->recsrc_wid = endpoint->volume_wid = -1; + endpoint->nrates = 3; + endpoint->rates[0] = 8000; + endpoint->rates[1] = 9600; + endpoint->rates[2] = 16000; + endpoint->name = widget->name; + endpoint->channels = 1; + endpoint->is_digital = 0; + endpoint->sizemask = widget->sizes; + endpoint->fmt_mask = AFMT_S16_LE; + endpoint->afg = codec->afg; + endpoint->min_rate = 8000; + endpoint->max_rate = 16000; + + return 0; +} + +int hdaudio_si3055_mixer_init(int dev, hdaudio_mixer_t * mixer, int cad, int top_group) +{ + return hdaudio_generic_mixer_init(dev, mixer, cad, top_group); +} diff -r 7656ac428350 kernel/drv/oss_hdaudio/oss_hdaudio.c --- a/kernel/drv/oss_hdaudio/oss_hdaudio.c Wed Oct 29 22:46:59 2008 +0200 +++ b/kernel/drv/oss_hdaudio/oss_hdaudio.c Tue Nov 04 23:07:49 2008 -0200 @@ -451,8 +451,8 @@ return portc->channels; } - if (arg < 2) - arg = 2; + if (arg < 1) + arg = 1; if (arg > adev->max_channels) { @@ -1460,7 +1460,7 @@ adev->mixer_dev = devc->mixer_dev; adev->min_rate = 8000; adev->max_rate = 192000; - adev->min_channels = 2; + adev->min_channels = 1; if (output_num == 0) adev->max_channels = 8; @@ -1582,7 +1582,7 @@ adev->mixer_dev = devc->mixer_dev; adev->min_rate = 8000; adev->max_rate = 192000; - adev->min_channels = 2; + adev->min_channels = 1; adev->max_channels = 2; adev->min_block = 128; /* Hardware limitation */ adev->min_fragments = 4; /* Vmix doesn't work without this */ diff -r 7656ac428350 setup/Linux/oss/build/install.sh --- a/setup/Linux/oss/build/install.sh Wed Oct 29 22:46:59 2008 +0200 +++ b/setup/Linux/oss/build/install.sh Tue Nov 04 23:07:49 2008 -0200 @@ -56,12 +56,6 @@ if ! test -f $OSSLIBDIR/objects/osscore.o then echo Error: OSS core module for $REGPARM kernel is not available in $OSSLIBDIR/objects - exit 1 -fi - -if ! test -f $OSSLIBDIR/modules/oss_ich.o -then - echo Error: OSS driver modules for $REGPARM kernel are not available exit 1 fi
_______________________________________________ oss-devel mailing list oss-devel@mailman.opensound.com http://mailman.opensound.com/mailman/listinfo/oss-devel