Last Updated: Friday, June 29 2001
So I was having lunch with Rich Bodo, and we ended up heading over to a Starbucks after the Mexican place we went to closed. He mentioned something about using winmodems as cheap telephony cards, and, well...
Pavel's page: http://www.suse.cz/development/ltmodem
Jamie's page: http://www.tantalophile.demon.co.uk/linmodem/
also see the subdirectories /ref/ and /jamie/ of the above site,
particularly /ref/LTNOTES/ which is a gold nugget.
Richard's page: http://www.close.u-net.com/ltmodem.html
Here are some links to stuff about the chips:
http://www.lucent.com/micro/K56flex/PN96053.html This is weird; this page says "the DSP hardware performs MIPS-intensive operations, such as K56flex and V.34 and V.32 modulation, while the host performs other less MIPS-intensive functions, such as V.42bis." Turns out that our dinky winmodems have some processing capability after all.
http://www.semiconductor.com/reports/asp/ShowSummary.asp?KeyColumn=862
http://www.semiconductor.com/reports/asp/ShowSummary.asp?KeyColumn=73
http://www.lucent.com/micro/K56flex/docs/OT00114.pdf
Hmm, ixj.c in drivers/telephony is almost 10,000 lines. Emacs refuses to color syntax highlight it, even.
PCI devices have four identification registers:
The vendor info for the MARS-2 chipset (Lucent's codename) is:
Vendor ID: 11c1 (Lucent Microelectronics) (now Agere)
Product ID: 0441 (56k modem)
Subvendor ID: 158d (Point Multimedia Systems, according to the Linux kernel)
Subproduct ID: 5517 (?)
The ixj.c driver seems to have the following initialization path:
6538: module_init(ixj_init);
6506: int __init ixj_init(void)
6454: #if defined(CONFIG_PCI)
6521: if (pci_present()) {
6522: if ((probe = ixj_probe_pci(&cnt)) < 0) {
6523: return probe;
6524: }
6525: }
6455: int __init ixj_probe_pci(int *cnt)
This I found to be a little disturbing:
3255: // NOTE: 3256: // The DAA can only go to SLEEP, RINGING or PULSEDIALING modes 3257: // if the PSTN line is on-hook. Failure to have the PSTN line 3258: // in the on-hook state WILL CAUSE A HARDWARE FAILURE OF THE 3259: // ALIS-A part. 3260: //
The module_init(x) stuff is obvious; it marks a function to be run when the module is inserted. Less obvious is that the kernel uses the __init declaration to mark a function to be run at boot time if the module is compiled into the kernel (<*> instead of <M> in menuconfig terms). The relevant bits are in include/linux/init.h and arch/X/vmlinux.lds, where X is the architecture. That last one is a ld script to munge the (boy, and I thought *I* was abusing macros) symbols defined with the __init macro so they run during kernel initialization. I haven't found the mechanism which triggers the __initcall assembly symbol hack depending on the compliation status of the module.
drivers/telephony/phonedev.c defines phone_register_device, which you call with a pointer to your struct phone_device and a unit parameter. I'm not sure what the unit parameter does, but I see ixj.c use PHONE_DEV_ANY (defined in linux/phonedev.h), so I guess we should use that too. There's a comment in drivers/telephony/phonedev.c which implies that the function can be called with something besides PHONE_DEV_ANY as the unit parameter, but there aren't any other drivers that do so.
struct phone_device is defined as:
struct phone_device {
struct phone_device *next;
struct file_operations *f_op;
int (*open) (struct phone_device *, struct file *);
int board; /* Device private index */
int minor; /* Assigned by phone_register_device */
};
struct file_operations is defined as:
struct file_operations {
struct module *owner;
loff_t (*llseek) (struct file *, loff_t, int);
ssize_t (*read) (struct file *, char *, size_t, loff_t *);
ssize_t (*write) (struct file *, const char *, size_t, loff_t *);
int (*readdir) (struct file *, void *, filldir_t);
unsigned int (*poll) (struct file *, struct poll_table_struct *);
int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);
int (*mmap) (struct file *, struct vm_area_struct *);
int (*open) (struct inode *, struct file *);
int (*flush) (struct file *);
int (*release) (struct inode *, struct file *);
int (*fsync) (struct file *, struct dentry *, int datasync);
int (*fasync) (int, struct file *, int);
int (*lock) (struct file *, int, struct file_lock *);
ssize_t (*readv) (struct file *, const struct iovec *, unsigned long, loff_t *);
ssize_t (*writev) (struct file *, const struct iovec *, unsigned long, loff_t *);
ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
};
The pci_enable_device function does:
There's a sample kernel module at http://www.cc.gatech.edu/classes/AY2001/cs3210_fall/labs/compile-part2.html. Check it out.
You compile a kernel module with something like:
cc -Wall -DMODULE -D__KERNEL__ -DLINUX -c -o ltphone.o ltphone.c
Wednesday June 27 2001
So the ltmodem people initialize a global variable, io_address, when
the pci utils starts up and detects the modem and stuff. This ends up
setting BaseAddress when io_init is called, and all the port io stuff
like dp_regread and dp_in_port and friends use the BaseAddress as
their offset to do port IO. We can use the pci_dev structure to do so instead.
Here's what I get when I use the new detection code:
Found memory range: cfffaf00-cfffafff Found IO port range Lucent Microelectronics 56k WinModem: d000-d007 Found IO port range Lucent Microelectronics 56k WinModem: cc00-ccff Found ltmodem Lucent Microelectronics 56k WinModem at irq 5
It looks like the first thing to try is to read the DSP version by creating and calling dp_modem_command from portIO.c with args 16,0,0. It looks like it puts stuff into offset + 0x37, which means we're going to be talking to the larger port range (cc00-ccff from above).
Friday, June 29 2001
So the ltmodem stuff calls find_modem to query the PCI registers and
find the location of the IO addresses. They're copied in the order
they are found, which is important for later.
port_io_init() defines two global variables, BaseAddress and BaseAddress2. port_io_init() is called from io_init() in ltmodem.c, with io_address[1] and io_address[2] as arguments, and assigns io_address[2] to BaseAddress, which means that if they're detecting the IO ranges in the same order that we are (why do they count from 1?), BaseAddress and friends are all pointing to IO ports in the larger second 256 port wide window.
Monday August 6 2001 Ah, back after Defcon craziness. Here we go...
dp_modem_command() calls dp_dsp_regread() and dp_regread() and dp_regwrite() and dp_bamil_rd7() if the command is 0x0c or 0x0e. Otherwise, calls dp_regwrite() and wait_for_core_read(). dp_regwrite() calls dp_out_port(). dp_out_port does a straight outb to BaseValue, which is a global set in dp_regwrite. The output port from dp_modem_command is 0x35, 0x36, and 0x37. I doubt that they mean port 0x35, 0x36, and 0x37. More likely they're offsets from BaseValue. - Set in ltphone_probe_pci() - BaseAddress (0x00) BaseAddressData (0x01) BaseAddress2 (0x02) - Set in dp_regwrite() - BaseValue (set to port) BaseAddressIndex (set to BaseAddress)
Ah, the dp_byte_x variables need to be explicitly updated in the absence of an interrupt handler. Fixed. Checksum code breaks things, though.
Wednesday March 29 2006
Talked to Gary Fleury 408 546 1550 about getting datasheets for the 1646T00.