/*************************************************************************** * __________ __ ___. * Open \______ \ ____ ____ | | _\_ |__ _______ ___ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ * \/ \/ \/ \/ \/ * * Module for the SI4709 FM Radio Chip of Samsung YP-R0 * - open the device (power on) * - close the device (power off) * - read registers * - write registers * - direct i2c access * - debug registers * * Copyright (c) 2012 Lorenzo Miori * * 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 software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY * KIND, either express or implied. * ****************************************************************************/ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include <../arch/arm/mach-mx37/iomux.h> #include "si4709.h" /* We use both name and number of the original si470x device in order * to exploit the default device name present in ROM */ #define SI4709_DEV_NAME "si470x" #define SI4709_DEV_NUM 240 #define LOG(f, x...) \ printk(SI4709_DEV_NAME ": " f, ## x) #define FM_GPIO_INT_IRQ IOMUX_TO_IRQ(MX37_PIN_GPIO1_4) static DEFINE_MUTEX(si4709_lock); static unsigned char reg_rw_buf[SI4702_REG_BYTE]; static bool is_si4709_init = 0; static bool is_si4709_power = 0; static int SI4709_open(struct inode* inode, struct file* filp); static int SI4709_release(struct inode* inode, struct file* filp); static int SI4709_read(struct file *filp, char *buffer, size_t count, loff_t *ppos); static int SI4709_ioctl(struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg); static int SI4709_detach(struct i2c_client* client); static int SI4709_probe(struct i2c_adapter* adapter); static int SI4709_attach(struct i2c_adapter* adapter, int address, int kind); static int si4709_read_reg(int reg, uint16_t* buf); static int si4709_i2c_read(int size, unsigned char* buf); static int si4709_write_reg(int reg, uint16_t val); static int si4709_i2c_write(int size, unsigned char* buf); static void si4709_power_on(void); static void si4709_power_off(void); irqreturn_t si4709_irq_handler (int irq, void* dev_id); static struct file_operations SI4709_fops = { .owner = THIS_MODULE, .open = SI4709_open, .read = SI4709_read, .release = SI4709_release, .ioctl = SI4709_ioctl, }; /* this is used by i2c_add_driver */ static struct i2c_driver SI4709_driver = { .driver = { .name = SI4709_DEV_NAME, }, .attach_adapter = SI4709_probe, .detach_client = SI4709_detach, }; static unsigned short normal_i2c[] = {SI4709_I2C_SLAVE_ADDR, I2C_CLIENT_END}; /* var. name MUST be normal_i2c */ I2C_CLIENT_INSMOD; /* DO NOT MISS THIS after normal_i2c!!! from i2c.h */ static struct i2c_client *si4709_i2c; static int SI4709_read(struct file *filp, char *buffer, size_t count, loff_t *ppos) { return 0; } /* When opening the device file, the chip will be powered up */ static int SI4709_open(struct inode* inode, struct file* filp) { si4709_power_on(); return 0; } /* When closing the device file, the chip will be powered off */ static int SI4709_release(struct inode* inode, struct file* filp) { si4709_power_off(); return 0; } static int SI4709_ioctl(struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg) { int ret = -1; sSi4709_t sData; sSi4709_i2c_t i2cData; switch(cmd) { case IOCTL_SI4709_INIT: ret = 0; break; case IOCTL_SI4709_CLOSE: ret = 0; break; case IOCTL_SI4709_WRITE_BYTE: if(!copy_from_user((void*)&sData, (const void*)arg, _IOC_SIZE(cmd))) { if(si4709_write_reg(sData.addr, sData.value) != -1) { ret = 0; } } break; case IOCTL_SI4709_READ_BYTE: if(!copy_from_user((void*)&sData, (const void*)arg, _IOC_SIZE(cmd))) { if(si4709_read_reg(sData.addr, &sData.value) != -1) { if(!copy_to_user((void*)arg, &sData, sizeof(sSi4709_t))) { ret = 0; } } } break; case IOCTL_SI4709_I2C_READ: if(!copy_from_user((void*)&i2cData, (const void*)arg, _IOC_SIZE(cmd))) { if(si4709_i2c_read(i2cData.size, i2cData.buf) != -1) { if(!copy_to_user((void*)arg, &i2cData, sizeof(i2cData))) { ret = 0; } } } break; case IOCTL_SI4709_I2C_WRITE: if(!copy_from_user((void*)&i2cData, (const void*)arg, _IOC_SIZE(cmd))) { if(si4709_i2c_write(i2cData.size, i2cData.buf) != -1) { ret = 0; } } break; default: break; } if (ret == -1) { LOG("ioctl failed!\n"); } return ret; } /* Hardware power management * Works setting up the RST pin (->GPIO: MX37_PIN_AUD5_RXC) */ static void si4709_power_on(void) { if (!is_si4709_power) { mxc_request_iomux(MX37_PIN_AUD5_RXC, 4); mxc_iomux_set_pad(MX37_PIN_AUD5_RXC, 5); mxc_set_gpio_direction(MX37_PIN_AUD5_RXC, 0); /* HIGH: turns on radio chip */ mxc_set_gpio_dataout(MX37_PIN_AUD5_RXC, 1); mdelay(100); is_si4709_power = 1; LOG("device powered on!\n"); } } static void si4709_power_off(void) { if (is_si4709_power) { /* LOW: turns off radio chip, registers are reseted */ mxc_set_gpio_dataout(MX37_PIN_AUD5_RXC, 0); mxc_free_iomux(MX37_PIN_AUD5_RXC, 4); mdelay(100); is_si4709_power = 0; LOG("device powered off!\n"); } } /* Macro + 2 methods slightly modified from: mxc_si4702 char device module available in kernel */ #define REG_to_BUF(reg) (((reg >= 0) && (reg < SI4702_STATUSRSSI))?\ (reg - SI4702_STATUSRSSI + SI4709_REG_NUM):\ ((reg >= SI4702_STATUSRSSI) && (reg < SI4709_REG_NUM))?\ (reg - SI4702_STATUSRSSI) : -1) static int si4709_read_reg(int reg, uint16_t *value) { int ret, index; if (NULL == si4709_i2c) return -1; index = REG_to_BUF(reg); if (-1 == index) return -1; mutex_lock(&si4709_lock); ret = i2c_master_recv(si4709_i2c, reg_rw_buf, SI4702_REG_BYTE); mutex_unlock(&si4709_lock); *value = (reg_rw_buf[index * 2] << 8) & 0xFF00; *value |= reg_rw_buf[index * 2 + 1] & 0x00FF; return ret; } /* Direct i2c channel reading */ static int si4709_i2c_read(int size, unsigned char *buf) { int ret; if (NULL == si4709_i2c) return -1; mutex_lock(&si4709_lock); ret = i2c_master_recv(si4709_i2c, buf, size); mutex_unlock(&si4709_lock); return ret; } static int si4709_write_reg(int reg, uint16_t value) { int index; int ret = -1; if (NULL == si4709_i2c) return -1; index = REG_to_BUF(reg); if (-1 == index) return -1; reg_rw_buf[index * 2] = (value & 0xFF00) >> 8; reg_rw_buf[index * 2 + 1] = value & 0x00FF; mutex_lock(&si4709_lock); ret = i2c_master_send(si4709_i2c, ®_rw_buf[SI4702_RW_OFFSET * 2], (SI4702_STATUSRSSI - SI4702_POWERCFG) * 2); mutex_unlock(&si4709_lock); return ret; } /* Direct i2c channel writing */ static int si4709_i2c_write(int size, unsigned char *buf) { int ret; if (NULL == si4709_i2c) return -1; mutex_lock(&si4709_lock); ret = i2c_master_send(si4709_i2c, buf, size); mutex_unlock(&si4709_lock); return ret; } /* I2C initialization stuff */ static int SI4709_detach(struct i2c_client* client) { int ret; ret = i2c_detach_client(client); if(ret < 0) LOG("detach failed %d\n", ret); return ret; } static int SI4709_probe(struct i2c_adapter* adapter) { int ret; ret = i2c_probe(adapter, &addr_data, &SI4709_attach); if(ret < 0) LOG("probe failed -> adapter id[%d] : %d\n", i2c_adapter_id(adapter), ret); return ret; } static int SI4709_attach(struct i2c_adapter* adapter, int address, int kind) { int ret; if(i2c_adapter_id(adapter) != 0) { LOG("Different i2c adapter\n"); return -ENODEV; } if(address != SI4709_I2C_SLAVE_ADDR) { LOG("Different i2c address\n"); return -ENODEV; } si4709_i2c = kzalloc(sizeof(struct i2c_client), GFP_KERNEL); if (si4709_i2c == NULL) return -ENOMEM; si4709_i2c->addr = address; si4709_i2c->adapter = adapter; si4709_i2c->driver = &SI4709_driver; si4709_i2c->flags = 0; strlcpy(si4709_i2c->name, SI4709_DEV_NAME, I2C_NAME_SIZE); ret = i2c_attach_client(si4709_i2c); if (ret < 0) LOG("i2c attach failed [%d]\n", i2c_adapter_id(adapter)); else LOG("i2c attached : adapter[%d] addr[0x%X]\n", i2c_adapter_id(adapter), si4709_i2c->addr); return ret; } /* This is to catch the interrupt of the GPIO pin of the radio chip */ irqreturn_t si4709_irq_handler (int irq, void* dev_id) { /* TODO */ LOG("IRQ callback!"); return IRQ_HANDLED; } /* Things to be done here: initialize pins and set RST pin to HIGH */ void si4709_initialize(void) { /* Read a "random" register in order to initialize register cache */ uint16_t dummy; si4709_read_reg(SI4702_POWERCFG, &dummy); /* Radio Chip GPIO pin setup (input, right?) */ mxc_request_iomux(MX37_PIN_GPIO1_4, 4); mxc_iomux_set_pad(MX37_PIN_GPIO1_4, 5); mxc_set_gpio_direction(MX37_PIN_GPIO1_4, 1); //0x84, 0x2 -> reversed parameters, to be checked set_irq_type(FM_GPIO_INT_IRQ, IRQT_FALLING); if(request_irq(FM_GPIO_INT_IRQ, si4709_irq_handler, IRQF_DISABLED, SI4709_DEV_NAME, NULL)) { LOG("si4709: IRQ Register Failed!\n"); return; } set_irq_wake(FM_GPIO_INT_IRQ, 1); //SOME TESTS // si4709_write_reg(SI4702_POWERCFG, 0x6001); // mdelay(500); // si4709_read_reg(SI4702_TEST1, &dummy); // si4709_write_reg(SI4702_POWERCFG, 0); // LOG("Magic: %i\n", dummy); is_si4709_init = 1; } static ssize_t si4709_show_registers(struct device *dev, struct device_attribute *attr, char *buf) { int i; ssize_t len = 0; len += sprintf(buf+len, "\n"); len += sprintf(buf+len, " 0 1 2 3 4 5 6 7 8 9 a b c d e f\n"); len += sprintf(buf+len, " -----------------------------------------------\n"); len += sprintf(buf+len, "[%02Xh]:", 0); for(i=0; idev.kobj, &si4709_attr_group); if( ret < 0 ) { LOG("si4709 sysfs create group error\n"); return -EINVAL; } return 0; } static int si4709attr_remove(struct platform_device *pdev) { sysfs_remove_group(&pdev->dev.kobj, &si4709_attr_group); return 0; } static struct platform_driver si4709attr_driver = { .probe = si4709attr_probe, .remove = si4709attr_remove, .driver = { .name = SI4709_DEV_NAME, .owner = THIS_MODULE, }, }; static struct platform_device si4709attr_device = { .name = SI4709_DEV_NAME, .id = 0, }; /* Turn on chip, attach i2c driver and device driver, initialize and then turn off */ static int __init SI4709_init(void) { int ret; si4709_power_on(); ret = i2c_add_driver(&SI4709_driver); if (ret) /* >0 means error for i2c_add_driver */ { LOG("Can't add i2c driver\n"); return ret; } ret = register_chrdev(SI4709_DEV_NUM, SI4709_DEV_NAME, &SI4709_fops); if (ret < 0) { LOG("Can't get major number for si4709 driver\n"); return ret; } si4709_initialize(); /* register platform device */ ret = platform_device_register(&si4709attr_device); if(ret) { LOG("failed to add platform device %s (%d) \n", si4709attr_device.name, ret); return ret; } ret = platform_driver_register(&si4709attr_driver); if(ret) { LOG("failed to add platrom driver %s (%d) \n", si4709attr_driver.driver.name, ret); return ret; } si4709_power_off(); return 0; } static void __exit SI4709_exit(void) { platform_driver_unregister(&si4709attr_driver); platform_device_unregister(&si4709attr_device); free_irq(FM_GPIO_INT_IRQ, si4709_irq_handler); mxc_free_iomux(MX37_PIN_GPIO1_4 ,4); unregister_chrdev(SI4709_DEV_NUM, SI4709_DEV_NAME); i2c_del_driver(&SI4709_driver); } module_init(SI4709_init); module_exit(SI4709_exit); MODULE_AUTHOR("Lorenzo Miori (C) 2012"); MODULE_DESCRIPTION("SI4709 FM Radio Chip Driver for Samsung YP-R0"); MODULE_SUPPORTED_DEVICE("si470x"); MODULE_LICENSE("GPL");