| /* |
| * Many thanks to Fred Seidel <seidel@metabox.de>, the |
| * designer of the RDS decoder hardware. With his help |
| * I was able to code this driver. |
| * Thanks also to Norberto Pellicci, Dominic Mounteney |
| * <DMounteney@pinnaclesys.com> and www.teleauskunft.de |
| * for good hints on finding Fred. It was somewhat hard |
| * to locate him here in Germany... [: |
| * |
| * Revision history: |
| * |
| * 2000-08-09 Robert Siemer <Robert.Siemer@gmx.de> |
| * RDS support for MiroSound PCM20 radio |
| */ |
| |
| #include <linux/module.h> |
| #include <linux/errno.h> |
| #include <linux/string.h> |
| #include <linux/init.h> |
| #include <linux/slab.h> |
| #include <asm/semaphore.h> |
| #include <asm/io.h> |
| #include "../../../sound/oss/aci.h" |
| #include "miropcm20-rds-core.h" |
| |
| #define DEBUG 0 |
| |
| static struct semaphore aci_rds_sem; |
| |
| #define RDS_DATASHIFT 2 /* Bit 2 */ |
| #define RDS_DATAMASK (1 << RDS_DATASHIFT) |
| #define RDS_BUSYMASK 0x10 /* Bit 4 */ |
| #define RDS_CLOCKMASK 0x08 /* Bit 3 */ |
| |
| #define RDS_DATA(x) (((x) >> RDS_DATASHIFT) & 1) |
| |
| |
| #if DEBUG |
| static void print_matrix(char array[], unsigned int length) |
| { |
| int i, j; |
| |
| for (i=0; i<length; i++) { |
| printk(KERN_DEBUG "aci-rds: "); |
| for (j=7; j>=0; j--) { |
| printk("%d", (array[i] >> j) & 0x1); |
| } |
| if (i%8 == 0) |
| printk(" byte-border\n"); |
| else |
| printk("\n"); |
| } |
| } |
| #endif /* DEBUG */ |
| |
| static int byte2trans(unsigned char byte, unsigned char sendbuffer[], int size) |
| { |
| int i; |
| |
| if (size != 8) |
| return -1; |
| for (i = 7; i >= 0; i--) |
| sendbuffer[7-i] = (byte & (1 << i)) ? RDS_DATAMASK : 0; |
| sendbuffer[0] |= RDS_CLOCKMASK; |
| |
| return 0; |
| } |
| |
| static int rds_waitread(void) |
| { |
| unsigned char byte; |
| int i=2000; |
| |
| do { |
| byte=inb(RDS_REGISTER); |
| i--; |
| } |
| while ((byte & RDS_BUSYMASK) && i); |
| |
| if (i) { |
| #if DEBUG |
| printk(KERN_DEBUG "rds_waitread()"); |
| print_matrix(&byte, 1); |
| #endif |
| return (byte); |
| } else { |
| printk(KERN_WARNING "aci-rds: rds_waitread() timeout...\n"); |
| return -1; |
| } |
| } |
| |
| /* don't use any ..._nowait() function if you are not sure what you do... */ |
| |
| static inline void rds_rawwrite_nowait(unsigned char byte) |
| { |
| #if DEBUG |
| printk(KERN_DEBUG "rds_rawwrite()"); |
| print_matrix(&byte, 1); |
| #endif |
| outb(byte, RDS_REGISTER); |
| } |
| |
| static int rds_rawwrite(unsigned char byte) |
| { |
| if (rds_waitread() >= 0) { |
| rds_rawwrite_nowait(byte); |
| return 0; |
| } else |
| return -1; |
| } |
| |
| static int rds_write(unsigned char cmd) |
| { |
| unsigned char sendbuffer[8]; |
| int i; |
| |
| if (byte2trans(cmd, sendbuffer, 8) != 0){ |
| return -1; |
| } else { |
| for (i=0; i<8; i++) { |
| rds_rawwrite(sendbuffer[i]); |
| } |
| } |
| return 0; |
| } |
| |
| static int rds_readcycle_nowait(void) |
| { |
| rds_rawwrite_nowait(0); |
| return rds_waitread(); |
| } |
| |
| static int rds_readcycle(void) |
| { |
| if (rds_rawwrite(0) < 0) |
| return -1; |
| return rds_waitread(); |
| } |
| |
| static int rds_read(unsigned char databuffer[], int datasize) |
| { |
| #define READSIZE (8*datasize) |
| |
| int i,j; |
| |
| if (datasize < 1) /* nothing to read */ |
| return 0; |
| |
| /* to be able to use rds_readcycle_nowait() |
| I have to waitread() here */ |
| if (rds_waitread() < 0) |
| return -1; |
| |
| memset(databuffer, 0, datasize); |
| |
| for (i=0; i< READSIZE; i++) |
| if((j=rds_readcycle_nowait()) < 0) { |
| return -1; |
| } else { |
| databuffer[i/8]|=(RDS_DATA(j) << (7-(i%8))); |
| } |
| |
| return 0; |
| } |
| |
| static int rds_ack(void) |
| { |
| int i=rds_readcycle(); |
| |
| if (i < 0) |
| return -1; |
| if (i & RDS_DATAMASK) { |
| return 0; /* ACK */ |
| } else { |
| printk(KERN_DEBUG "aci-rds: NACK\n"); |
| return 1; /* NACK */ |
| } |
| } |
| |
| int aci_rds_cmd(unsigned char cmd, unsigned char databuffer[], int datasize) |
| { |
| int ret; |
| |
| if (down_interruptible(&aci_rds_sem)) |
| return -EINTR; |
| |
| rds_write(cmd); |
| |
| /* RDS_RESET doesn't need further processing */ |
| if (cmd!=RDS_RESET && (rds_ack() || rds_read(databuffer, datasize))) |
| ret = -1; |
| else |
| ret = 0; |
| |
| up(&aci_rds_sem); |
| |
| return ret; |
| } |
| EXPORT_SYMBOL(aci_rds_cmd); |
| |
| int __init attach_aci_rds(void) |
| { |
| init_MUTEX(&aci_rds_sem); |
| return 0; |
| } |
| |
| void __exit unload_aci_rds(void) |
| { |
| } |
| MODULE_LICENSE("GPL"); |