diff options
Diffstat (limited to 'drivers/char/mxc_si4702.c')
-rw-r--r-- | drivers/char/mxc_si4702.c | 1223 |
1 files changed, 1223 insertions, 0 deletions
diff --git a/drivers/char/mxc_si4702.c b/drivers/char/mxc_si4702.c new file mode 100644 index 000000000000..7d36f2902bb4 --- /dev/null +++ b/drivers/char/mxc_si4702.c @@ -0,0 +1,1223 @@ +/* + * linux/drivers/char/mxc_si4702.c + * + * Copyright 2008-2010 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/kernel.h> +#include <linux/cdev.h> +#include <linux/i2c.h> +#include <linux/delay.h> +#include <linux/fs.h> +#include <linux/regulator/consumer.h> +#include <linux/err.h> +#include <linux/slab.h> +#include <linux/mxc_si4702.h> +#include <linux/fsl_devices.h> +#include <linux/uaccess.h> + +#define SI4702_DEV_NAME "si4702" +#define DEV_MAJOR 0 /* this could be module param */ +#define DEV_MINOR 0 +#define DEV_BASE_MINOR 0 +#define DEV_MINOR_COUNT 256 +#define SI4702_I2C_ADDR 0x10 /* 7bits I2C address */ +#define DELAY_WAIT 0xffff /* loop_counter max value */ +/* register define */ +#define SI4702_DEVICEID 0x00 +#define SI4702_CHIPID 0x01 +#define SI4702_POWERCFG 0x02 +#define SI4702_CHANNEL 0x03 +#define SI4702_SYSCONFIG1 0x04 +#define SI4702_SYSCONFIG2 0x05 +#define SI4702_SYSCONFIG3 0x06 +#define SI4702_TEST1 0x07 +#define SI4702_TEST2 0x08 +#define SI4702_B00TCONFIG 0x09 +#define SI4702_STATUSRSSI 0x0A +#define SI4702_READCHAN 0x0B +#define SI4702_REG_NUM 0x10 +#define SI4702_REG_BYTE (SI4702_REG_NUM * 2) +#define SI4702_DEVICE_ID 0x1242 +#define SI4702_RW_REG_NUM (SI4702_STATUSRSSI - SI4702_POWERCFG) +#define SI4702_RW_OFFSET \ + (SI4702_REG_NUM - SI4702_STATUSRSSI + SI4702_POWERCFG) + +#define SI4702_SPACE_MASK 0x0030 +#define SI4702_SPACE_200K 0x0 +#define SI4702_SPACE_100K 0x10 +#define SI4702_SPACE_50K 0x20 + +#define SI4702_BAND_MASK 0x00c0 +#define SI4702_BAND_LSB 6 + +#define SI4702_SEEKTH_MASK 0xff00 +#define SI4702_SEEKTH_LSB 8 + +#define SI4702_SNR_MASK 0x00f0 +#define SI4702_SNR_LSB 4 + +#define SI4702_CNT_MASK 0x000f +#define SI4702_CNT_LSB 0 + +#define SI4702_VOL_MASK 0x000f +#define SI4702_VOL_LSB 0 + +#define SI4702_CHAN_MASK 0x03ff +#define SI4702_TUNE_BIT 0x8000 +#define SI4702_STC_BIT 0x4000 +#define SI4702_DMUTE_BIT 0x4000 +#define SI4702_SEEKUP_BIT 0x0200 +#define SI4702_SEEK_BIT 0x0100 +#define SI4702_SF_BIT 0x2000 +#define SI4702_ENABLE_BIT 0x0001 +#define SI4702_DISABLE_BIT 0x0040 + +enum { + BAND_USA = 0, + BAND_JAP_W, + BAND_JAP +}; + +struct si4702_info { + int min_band; + int max_band; + int space; + int volume; + int channel; + int mute; +}; + +struct si4702_drvdata { + struct regulator *vio; + struct regulator *vdd; + struct class *radio_class; + struct si4702_info info; + /*by default, dev major is zero, and it's alloc dynamicaly. */ + int major; + int minor; + struct cdev *cdev; + int count; /* open count */ + struct i2c_client *client; + unsigned char reg_rw_buf[SI4702_REG_BYTE]; + struct mxc_fm_platform_data *plat_data; +}; + +static struct si4702_drvdata *si4702_drvdata; + +DEFINE_SPINLOCK(count_lock); + +#ifdef DEBUG +static void si4702_dump_reg(void) +{ + int i, j; + unsigned char *reg_rw_buf; + + if (NULL == si4702_drvdata) + return; + + reg_rw_buf = si4702_drvdata->reg_rw_buf; + + for (i = 0; i < 10; i++) { + j = i * 2 + 12; + pr_debug("reg[%02d] = %04x\n", i, + ((reg_rw_buf[j] << 8) & 0xFF00) + + (reg_rw_buf[j + 1] & 0x00FF)); + } + for (; i < 16; i++) { + j = (i - 10) * 2; + pr_debug("reg[%02d] = %04x\n", i, + ((reg_rw_buf[j] << 8) & 0xFF00) + + (reg_rw_buf[j + 1] & 0x00FF)); + } +} +#else +static void si4702_dump_reg(void) +{ +} +#endif /* DEBUG */ + +/* + *check the si4702 spec for the read/write concequence. + * + *0 2 A F0 A F + *------------------------------- + * buf:0 2 A F + */ +#define REG_to_BUF(reg) (((reg >= 0) && (reg < SI4702_STATUSRSSI)) ? \ + (reg - SI4702_STATUSRSSI + SI4702_REG_NUM) : \ + ((reg >= SI4702_STATUSRSSI) && (reg < SI4702_REG_NUM)) ? \ + (reg - SI4702_STATUSRSSI) : -1) + +static int si4702_read_reg(const int reg, u16 *value) +{ + int ret, index; + unsigned char *reg_rw_buf; + + if (NULL == si4702_drvdata) + return -1; + + reg_rw_buf = si4702_drvdata->reg_rw_buf; + + index = REG_to_BUF(reg); + + if (-1 == index) + return -1; + + ret = + i2c_master_recv(si4702_drvdata->client, reg_rw_buf, + SI4702_REG_BYTE); + + *value = (reg_rw_buf[index * 2] << 8) & 0xFF00; + *value |= reg_rw_buf[index * 2 + 1] & 0x00FF; + + return ret < 0 ? ret : 0; +} + +static int si4702_write_reg(const int reg, const u16 value) +{ + int index, ret; + unsigned char *reg_rw_buf; + + if (NULL == si4702_drvdata) + return -1; + + reg_rw_buf = si4702_drvdata->reg_rw_buf; + + 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; + + ret = i2c_master_send(si4702_drvdata->client, + ®_rw_buf[SI4702_RW_OFFSET * 2], + (SI4702_STATUSRSSI - SI4702_POWERCFG) * 2); + return ret < 0 ? ret : 0; +} + +static void si4702_gpio_get(void) +{ + if (NULL == si4702_drvdata) + return; + + si4702_drvdata->plat_data->gpio_get(); +} + +static void si4702_gpio_put(void) +{ + if (NULL == si4702_drvdata) + return; + + si4702_drvdata->plat_data->gpio_put(); +} + +static void si4702_reset(void) +{ + if (NULL == si4702_drvdata) + return; + + si4702_drvdata->plat_data->reset(); +} + +static void si4702_clock_en(int flag) +{ + if (NULL == si4702_drvdata) + return; + + si4702_drvdata->plat_data->clock_ctl(flag); +} + +static int si4702_id_detect(struct i2c_client *client) +{ + int ret, index; + unsigned int ID = 0; + unsigned char reg_rw_buf[SI4702_REG_BYTE]; + + si4702_gpio_get(); + si4702_reset(); + si4702_clock_en(1); + + ret = i2c_master_recv(client, (char *)reg_rw_buf, SI4702_REG_BYTE); + + si4702_gpio_put(); + + if (ret < 0) + return ret; + + index = REG_to_BUF(SI4702_DEVICEID); + if (index < 0) + return index; + + ID = (reg_rw_buf[index * 2] << 8) & 0xFF00; + ID |= reg_rw_buf[index * 2 + 1] & 0x00FF; + + return ID; +} + +/* valid args 50/100/200 */ +static int si4702_set_space(int space) +{ + u16 reg; + int ret; + struct si4702_info *info; + + if (NULL == si4702_drvdata) + return -1; + + ret = si4702_read_reg(SI4702_SYSCONFIG2, ®); + if (ret == -1) + return ret; + + reg &= ~SI4702_SPACE_MASK; + switch (space) { + case 50: + reg |= SI4702_SPACE_50K; + break; + case 100: + reg |= SI4702_SPACE_100K; + break; + case 200: + ret |= SI4702_SPACE_200K; + break; + default: + return -1; + } + + ret = si4702_write_reg(SI4702_SYSCONFIG2, reg); + if (ret == -1) + return ret; + + info = &si4702_drvdata->info; + info->space = space; + return 0; +} + +static int si4702_set_band_range(int band) +{ + u16 reg; + int ret, band_min, band_max; + struct si4702_info *info; + + if (NULL == si4702_drvdata) + return -1; + + switch (band) { + case BAND_USA: + band_min = 87500; + band_max = 108000; + break; + case BAND_JAP_W: + band_min = 76000; + band_max = 108000; + break; + case BAND_JAP: + band_min = 76000; + band_max = 90000; + break; + default: + return -1; + } + + ret = si4702_read_reg(SI4702_SYSCONFIG2, ®); + if (ret == -1) + return ret; + + reg = (reg & ~SI4702_BAND_MASK) + | ((band << SI4702_BAND_LSB) & SI4702_BAND_MASK); + ret = si4702_write_reg(SI4702_SYSCONFIG2, reg); + if (ret == -1) + return ret; + + info = &si4702_drvdata->info; + info->min_band = band_min; + info->max_band = band_max; + return 0; +} + +static int si4702_set_seekth(u8 seekth) +{ + u16 reg; + int ret; + + if (NULL == si4702_drvdata) + return -1; + + ret = si4702_read_reg(SI4702_SYSCONFIG2, ®); + if (ret == -1) + return ret; + + reg = + (reg & ~SI4702_SEEKTH_MASK) | ((seekth << SI4702_SEEKTH_LSB) & + SI4702_SEEKTH_MASK); + ret = si4702_write_reg(SI4702_SYSCONFIG2, reg); + if (ret == -1) + return ret; + + return 0; +} + +static int si4702_set_sksnr(u8 sksnr) +{ + u16 reg; + int ret; + + if (NULL == si4702_drvdata) + return -1; + + ret = si4702_read_reg(SI4702_SYSCONFIG3, ®); + if (ret == -1) + return ret; + + reg = + (reg & ~SI4702_SNR_MASK) | ((sksnr << SI4702_SNR_LSB) & + SI4702_SNR_MASK); + ret = si4702_write_reg(SI4702_SYSCONFIG3, reg); + if (ret == -1) + return ret; + + return 0; +} + +static int si4702_set_skcnt(u8 skcnt) +{ + u16 reg; + int ret; + + if (NULL == si4702_drvdata) + return -1; + + ret = si4702_read_reg(SI4702_SYSCONFIG3, ®); + if (ret == -1) + return ret; + + reg = (reg & ~SI4702_CNT_MASK) | (skcnt & SI4702_CNT_MASK); + ret = si4702_write_reg(SI4702_SYSCONFIG3, reg); + if (ret == -1) + return ret; + + return 0; +} + +static int si4702_set_vol(int vol) +{ + u16 reg; + int ret; + struct si4702_info *info; + + if (NULL == si4702_drvdata) + return -1; + + ret = si4702_read_reg(SI4702_SYSCONFIG2, ®); + if (ret == -1) + return ret; + + reg = (reg & ~SI4702_VOL_MASK) | (vol & SI4702_VOL_MASK); + ret = si4702_write_reg(SI4702_SYSCONFIG2, reg); + if (ret == -1) + return ret; + + info = &si4702_drvdata->info; + info->volume = vol; + + return 0; +} + +static u8 si4702_channel_select(u32 freq) +{ + u16 loop_counter = 0; + s16 channel; + u16 si4702_reg_data; + u8 error_ind = 0; + struct i2c_client *client; + struct si4702_info *info; + + if (NULL == si4702_drvdata) + return -1; + + info = &si4702_drvdata->info; + client = si4702_drvdata->client; + + dev_info(&client->dev, "Input frequnce is %d\n", freq); + if (freq < 76000 || freq > 108000) { + dev_err(&client->dev, "Input frequnce is invalid\n"); + return -1; + } + /* convert freq to channel */ + channel = (freq - info->min_band) / info->space; + + si4702_reg_data = SI4702_TUNE_BIT | (channel & SI4702_CHAN_MASK); + /* set channel */ + error_ind = si4702_write_reg(SI4702_CHANNEL, si4702_reg_data); + if (error_ind) { + dev_err(&client->dev, "Failed to set channel\n"); + return -1; + } + dev_info(&client->dev, "Set channel to %d\n", channel); + + /* wait for STC == 1 */ + do { + error_ind = + si4702_read_reg(SI4702_STATUSRSSI, &si4702_reg_data); + + if (error_ind) { + dev_err(&client->dev, "Failed to read setted STC\n"); + return -1; + } + if ((si4702_reg_data & SI4702_STC_BIT) != 0) + break; + } while (++loop_counter < DELAY_WAIT); + + /* check loop_counter */ + if (loop_counter >= DELAY_WAIT) { + dev_err(&client->dev, "Can't wait for STC bit set"); + return -1; + } + dev_info(&client->dev, "loop counter %d\n", loop_counter); + + loop_counter = 0; + /* clear tune bit */ + error_ind = si4702_write_reg(SI4702_CHANNEL, 0); + + if (error_ind) { + dev_err(&client->dev, "Failed to set stop tune\n"); + return -1; + } + + /* wait for STC == 0 */ + do { + error_ind = + si4702_read_reg(SI4702_STATUSRSSI, &si4702_reg_data); + + if (error_ind) { + dev_err(&client->dev, "Failed to set read STC\n"); + return -1; + } + if ((si4702_reg_data & SI4702_STC_BIT) == 0) + break; + } while (++loop_counter < DELAY_WAIT); + + /* check loop_counter */ + if (loop_counter >= DELAY_WAIT) { + dev_err(&client->dev, "Can't wait for STC bit clean"); + return -1; + } + dev_info(&client->dev, "loop counter %d\n", loop_counter); + + /* read RSSI */ + error_ind = si4702_read_reg(SI4702_READCHAN, &si4702_reg_data); + + if (error_ind) { + dev_err(&client->dev, "Failed to read RSSI\n"); + return -1; + } + + channel = si4702_reg_data & SI4702_CHAN_MASK; + dev_info(&client->dev, "seek finish: channel(%d)\n", channel); + + return 0; +} + +static s32 si4702_channel_seek(s16 dir) +{ + u16 loop_counter = 0; + u16 si4702_reg_data, reg_power_cfg; + u8 error_ind = 0; + u32 channel, freq; + struct i2c_client *client; + struct si4702_info *info; + + if (NULL == si4702_drvdata) + return -1; + + info = &si4702_drvdata->info; + client = si4702_drvdata->client; + + error_ind = si4702_read_reg(SI4702_POWERCFG, ®_power_cfg); + + if (info->mute) { + /* check disable mute */ + reg_power_cfg &= ~SI4702_DMUTE_BIT; + } else { + reg_power_cfg |= SI4702_DMUTE_BIT; + } + + if (dir) { + reg_power_cfg |= SI4702_SEEKUP_BIT; + } else { + reg_power_cfg &= ~SI4702_SEEKUP_BIT; + } + /* start seek */ + reg_power_cfg |= SI4702_SEEK_BIT; + error_ind = si4702_write_reg(SI4702_POWERCFG, reg_power_cfg); + + if (error_ind) { + dev_err(&client->dev, "Failed to set seek start bit\n"); + return -1; + } + + /* wait STC == 1 */ + do { + error_ind = + si4702_read_reg(SI4702_STATUSRSSI, &si4702_reg_data); + if (error_ind) { + dev_err(&client->dev, "Failed to read STC bit\n"); + return -1; + } + + if ((si4702_reg_data & SI4702_STC_BIT) != 0) + break; + } while (++loop_counter < DELAY_WAIT); + + /* clear seek bit */ + reg_power_cfg &= ~SI4702_SEEK_BIT; + error_ind = si4702_write_reg(SI4702_POWERCFG, reg_power_cfg); + if (error_ind) { + dev_err(&client->dev, "Failed to stop seek\n"); + return -1; + } + + if (loop_counter >= DELAY_WAIT) { + dev_err(&client->dev, "Can't wait for STC bit set\n"); + return -1; + } + + /* check whether SF==1 (seek failed bit) */ + if ((si4702_reg_data & SI4702_SF_BIT) != 0) { + dev_err(&client->dev, "Failed to seek any channel\n"); + return -1; + } + + loop_counter = 0; + /* wait STC == 0 */ + do { + error_ind = + si4702_read_reg(SI4702_STATUSRSSI, &si4702_reg_data); + + if (error_ind) { + dev_err(&client->dev, + "Failed to wait STC bit to clear\n"); + return -1; + } + if ((si4702_reg_data & SI4702_STC_BIT) == 0) + break; + } while (++loop_counter < DELAY_WAIT); + + /* check loop_counter */ + if (loop_counter >= DELAY_WAIT) { + dev_err(&client->dev, "Can't wait for STC bit clean"); + return -1; + } + + error_ind = si4702_read_reg(SI4702_READCHAN, &si4702_reg_data); + + if (error_ind) { + dev_err(&client->dev, "I2C simulate failed\n"); + return -1; + } + + channel = si4702_reg_data & SI4702_CHAN_MASK; + freq = channel * info->space + info->min_band; + dev_err(&client->dev, + "seek finish: channel(%d), freq(%dKHz)\n", channel, freq); + + return 0; +} + +static int si4702_startup(void) +{ + u16 magic = 0, id; + struct i2c_client *client; + struct mxc_fm_platform_data *data; + + if (NULL == si4702_drvdata) + return -1; + + if (si4702_drvdata->vio) + regulator_enable(si4702_drvdata->vio); + if (si4702_drvdata->vdd) + regulator_enable(si4702_drvdata->vdd); + data = si4702_drvdata->plat_data; + client = si4702_drvdata->client; + + /* read prior to write, otherwise write op will fail */ + si4702_read_reg(SI4702_DEVICEID, &id); + dev_err(&client->dev, "si4702: DEVICEID: 0x%x\n", id); + + si4702_clock_en(1); + msleep(100); + + /* disable mute, stereo, seek down, powerup */ + si4702_write_reg(SI4702_POWERCFG, SI4702_DMUTE_BIT | SI4702_ENABLE_BIT); + msleep(500); + si4702_read_reg(SI4702_TEST1, &magic); + if (magic != 0x3C04) + dev_err(&client->dev, "magic number 0x%x.\n", magic); + /* close tune, set channel to 0 */ + si4702_write_reg(SI4702_CHANNEL, 0); + /* disable interrupt, disable GPIO */ + si4702_write_reg(SI4702_SYSCONFIG1, 0); + /* set volume to middle level */ + si4702_set_vol(0xf); + + si4702_set_space(data->space); + si4702_set_band_range(data->band); + si4702_set_seekth(data->seekth); + si4702_set_skcnt(data->skcnt); + si4702_set_sksnr(data->sksnr); + + return 0; +} + +static void si4702_shutdown(void) +{ + if (NULL == si4702_drvdata) + return; + + si4702_write_reg(SI4702_POWERCFG, SI4702_DMUTE_BIT | + SI4702_ENABLE_BIT | SI4702_DISABLE_BIT); + msleep(100); + si4702_clock_en(0); + + if (si4702_drvdata->vdd) + regulator_disable(si4702_drvdata->vdd); + if (si4702_drvdata->vio) + regulator_disable(si4702_drvdata->vio); +} + +enum { + FM_STARTUP = 0, + FM_SHUTDOWN, + FM_RESET, + FM_VOLUP, + FM_VOLDOWN, + FM_SEEK_UP, + FM_SEEK_DOWN, + FM_MUTEON, + FM_MUTEDIS, + FM_SEL, + FM_SEEKTH, + FM_DL, + FM_CTL_MAX +}; + +static const char *const fm_control[FM_CTL_MAX] = { + [FM_STARTUP] = "start", + [FM_SHUTDOWN] = "halt", + [FM_RESET] = "reset", + [FM_VOLUP] = "volup", + [FM_VOLDOWN] = "voldown", + [FM_SEEK_UP] = "seeku", + [FM_SEEK_DOWN] = "seekd", + [FM_MUTEON] = "mute", + [FM_MUTEDIS] = "muted", + [FM_SEL] = "select", + [FM_SEEKTH] = "seekth", + [FM_DL] = "delay" +}; + +static int cmd(unsigned int index, int arg) +{ + struct i2c_client *client; + struct mxc_fm_platform_data *plat_data; + + if (NULL == si4702_drvdata) + return -1; + + client = si4702_drvdata->client; + plat_data = si4702_drvdata->plat_data; + + switch (index) { + case FM_SHUTDOWN: + dev_err(&client->dev, "FM_SHUTDOWN\n"); + si4702_shutdown(); + break; + case FM_STARTUP: + dev_err(&client->dev, "FM_STARTUP\n"); + si4702_reset(); + si4702_startup(); + break; + case FM_RESET: + dev_err(&client->dev, "FM_RESET\n"); + si4702_reset(); + break; + case FM_SEEK_DOWN: + dev_err(&client->dev, "SEEK DOWN\n"); + si4702_channel_seek(0); + break; + case FM_SEEK_UP: + dev_err(&client->dev, "SEEK UP\n"); + si4702_channel_seek(1); + break; + case FM_SEL: + dev_err(&client->dev, "select %d\n", arg * 100); + si4702_channel_select(arg * 100); + break; + case FM_SEEKTH: + dev_err(&client->dev, "seekth = %d\n", arg); + si4702_set_seekth(arg); + break; + case FM_DL: + dev_err(&client->dev, "delay = %d\n", arg); + break; + default: + dev_err(&client->dev, "error command\n"); + break; + } + return 0; +} + +static ssize_t si4702_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct si4702_drvdata *drv_data = dev_get_drvdata(dev); + u16 device_id; + + dev_err(&(drv_data->client->dev), "si4702 show\n"); + si4702_read_reg(SI4702_DEVICEID, &device_id); + pr_info("device id %x\n", device_id); + si4702_dump_reg(); + return 0; +} + +static ssize_t si4702_store(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t count) +{ + int state = 0; + const char *const *s; + char *p = NULL; + int error; + int len, arg = 0; + struct si4702_drvdata *drv_data = dev_get_drvdata(dev); + struct i2c_client *client = drv_data->client; + + dev_err(&client->dev, "si4702 store %d\n", count); + + p = memchr(buf, ' ', count); + if (p) { + len = p - buf; + *p = '\0'; + } else + len = count; + + len -= 1; + dev_err(&client->dev, "cmd %s\n", buf); + + for (s = &fm_control[state]; state < FM_CTL_MAX; s++, state++) { + if (*s && !strncmp(buf, *s, len)) { + break; + } + } + if (state < FM_CTL_MAX && *s) { + if (p) + arg = simple_strtoul(p + 1, NULL, 0); + dev_err(&client->dev, "arg = %d\n", arg); + error = cmd(state, arg); + } else { + dev_err(&client->dev, "error cmd\n"); + error = -EINVAL; + } + + return error ? error : count; +} + +static int ioctl_si4702(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + void __user *argp = (void __user *)arg; + int mute = 0; + u16 data; + int error; + u8 volume; + unsigned int freq; + int dir; + struct i2c_client *client; + struct si4702_info *info; + + if (NULL == si4702_drvdata) + return -1; + + info = &si4702_drvdata->info; + client = si4702_drvdata->client; + + dev_err(&client->dev, "ioctl, cmd: 0x%x, arg: 0x%lx\n", cmd, arg); + + switch (cmd) { + case SI4702_SETVOLUME: + /* get volume from user */ + if (copy_from_user(&volume, argp, sizeof(u8))) { + + dev_err(&client->dev, + "ioctl, copy volume value from user failed\n"); + return -EFAULT; + } + dev_err(&client->dev, "volume %d\n", volume); + /* refill the register value */ + volume &= 0x0f; + if (info->mute) + error = si4702_write_reg(SI4702_POWERCFG, 0x0001); + else + error = si4702_write_reg(SI4702_POWERCFG, 0x4001); + + error = si4702_write_reg(SI4702_CHANNEL, 0); + error = si4702_write_reg(SI4702_SYSCONFIG1, 0); + error = si4702_write_reg(SI4702_SYSCONFIG2, 0x0f10 | volume); + if (error) { + dev_err(&client->dev, "ioctl, set volume failed\n"); + return -EFAULT; + } + /* renew the device info */ + info->volume = volume; + + break; + case SI4702_GETVOLUME: + /* just copy volume value to user */ + if (copy_to_user(argp, &(info->volume), sizeof(unsigned int))) { + dev_err(&client->dev, "ioctl, copy to user failed\n"); + return -EFAULT; + } + break; + case SI4702_MUTEON: + mute = 1; + case SI4702_MUTEOFF: + if (mute) { + /* enable mute */ + si4702_read_reg(SI4702_POWERCFG, &data); + data &= 0x00FF; + error = si4702_write_reg(SI4702_POWERCFG, data); + } else { + si4702_read_reg(SI4702_POWERCFG, &data); + data &= 0x00FF; + data |= 0x4000; + error = si4702_write_reg(SI4702_POWERCFG, data); + } + if (error) { + dev_err(&client->dev, "ioctl, set mute failed\n"); + return -EFAULT; + } + break; + case SI4702_SELECT: + if (copy_from_user(&freq, argp, sizeof(unsigned int))) { + + dev_err(&client->dev, + "ioctl, copy frequence from user failed\n"); + return -EFAULT; + } + /* check frequence */ + if (freq > info->max_band || freq < info->min_band) { + dev_err(&client->dev, + "the frequence select is out of band\n"); + return -EINVAL; + } + if (si4702_channel_select(freq)) { + dev_err(&client->dev, + "ioctl, failed to select channel\n"); + return -EFAULT; + } + break; + case SI4702_SEEK: + if (copy_from_user(&dir, argp, sizeof(int))) { + + dev_err(&client->dev, "ioctl, copy from user failed\n"); + return -EFAULT; + } + /* get seeked channel */ + dir = si4702_channel_seek(dir); + if (dir == -1) { + return -EAGAIN; + } else if (dir == -2) { + return -EFAULT; + } + if (copy_to_user(argp, &dir, sizeof(int))) { + + dev_err(&client->dev, + "ioctl, copy seek frequnce to user failed\n"); + return -EFAULT; + } + break; + default: + dev_err(&client->dev, "SI4702: Invalid ioctl command\n"); + return -EINVAL; + + } + return 0; +} + +static int open_si4702(struct inode *inode, struct file *file) +{ + struct i2c_client *client; + + if (NULL == si4702_drvdata) + return -1; + + client = si4702_drvdata->client; + + spin_lock(&count_lock); + if (si4702_drvdata->count != 0) { + dev_err(&client->dev, "device has been open already\n"); + spin_unlock(&count_lock); + return -EBUSY; + } + si4702_drvdata->count++; + spin_unlock(&count_lock); + + /* request and active GPIO */ + si4702_gpio_get(); + /* reset the si4702 from it's reset pin */ + si4702_reset(); + + /* startup si4702 */ + if (si4702_startup()) { + spin_lock(&count_lock); + si4702_drvdata->count--; + spin_unlock(&count_lock); + return -ENODEV; + } + + return 0; +} + +static int release_si4702(struct inode *inode, struct file *file) +{ + struct i2c_client *client; + + if (NULL == si4702_drvdata) + return -1; + + client = si4702_drvdata->client; + + dev_err(&client->dev, "release\n"); + /* software shutdown */ + si4702_shutdown(); + /* inactive, free GPIO, cut power */ + si4702_gpio_put(); + + spin_lock(&count_lock); + si4702_drvdata->count--; + spin_unlock(&count_lock); + + return 0; +} + +static int si4702_suspend(struct i2c_client *client, pm_message_t state) +{ + return 0; +} + +static int si4702_resume(struct i2c_client *client) +{ + return 0; +} + +static struct device_attribute si4702_dev_attr = { + .attr = { + .name = "si4702_ctl", + .mode = S_IRUSR | S_IWUSR, + }, + .show = si4702_show, + .store = si4702_store, +}; + +static struct file_operations si4702_fops = { + .owner = THIS_MODULE, + .open = open_si4702, + .release = release_si4702, + .ioctl = ioctl_si4702, +}; + +static int __devinit si4702_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int ret = 0; + struct mxc_fm_platform_data *plat_data; + struct si4702_drvdata *drv_data; + struct device *dev; + + dev_info(&client->dev, "si4702 device probe process start.\n"); + + plat_data = (struct mxc_fm_platform_data *)client->dev.platform_data; + if (plat_data == NULL) { + dev_err(&client->dev, "lack of platform data!\n"); + return -ENODEV; + } + + drv_data = kmalloc(sizeof(struct si4702_drvdata), GFP_KERNEL); + if (drv_data == NULL) { + dev_err(&client->dev, "lack of kernel memory!\n"); + return -ENOMEM; + } + memset(drv_data, 0, sizeof(struct si4702_drvdata)); + drv_data->plat_data = plat_data; + drv_data->major = DEV_MAJOR; + drv_data->minor = DEV_MINOR; + drv_data->count = 0; + + /*enable power supply */ + if (plat_data->reg_vio != NULL) { + drv_data->vio = regulator_get(&client->dev, plat_data->reg_vio); + if (drv_data->vio == ERR_PTR(-ENOENT)) + goto free_drv_data; + regulator_enable(drv_data->vio); + } + + /* here, we assume that vio and vdd are not the same */ + if (plat_data->reg_vdd != NULL) { + drv_data->vdd = regulator_get(&client->dev, plat_data->reg_vdd); + if (drv_data->vdd == ERR_PTR(-ENOENT)) + goto disable_vio; + regulator_enable(drv_data->vdd); + } + + /*attach client and check device id */ + if (SI4702_DEVICE_ID != si4702_id_detect(client)) { + dev_err(&client->dev, "id wrong.\n"); + goto disable_vdd; + } + dev_info(&client->dev, "chip id %x detect.\n", SI4702_DEVICE_ID); + drv_data->client = client; + + /*user interface begain */ + /*create device file in sysfs as a user interface, + * also for debug support */ + ret = device_create_file(&client->dev, &si4702_dev_attr); + if (ret) { + dev_err(&client->dev, "create device file failed!\n"); + goto gpio_put; /* shall i use some meanful error code? */ + } + + /*create a char dev for application code access */ + if (drv_data->major) { + ret = register_chrdev(drv_data->major, "si4702", &si4702_fops);; + } else { + ret = register_chrdev(0, "si4702", &si4702_fops); + } + + if (drv_data->major == 0) + drv_data->major = ret; + + /* create class and device for udev information */ + drv_data->radio_class = class_create(THIS_MODULE, "radio"); + if (IS_ERR(drv_data->radio_class)) { + dev_err(&client->dev, "SI4702: failed to create radio class\n"); + goto char_dev_remove; + } + + dev = device_create(drv_data->radio_class, NULL, + MKDEV(drv_data->major, drv_data->minor), NULL, + "si4702"); + if (IS_ERR(dev)) { + dev_err(&client->dev, + "SI4702: failed to create radio class device\n"); + goto class_remove; + } + /*User interface end */ + dev_set_drvdata(&client->dev, drv_data); + si4702_drvdata = drv_data; + + si4702_gpio_get(); + dev_info(&client->dev, "si4702 device probe successfully.\n"); + si4702_shutdown(); + + return 0; + +class_remove: + class_destroy(drv_data->radio_class); +char_dev_remove: + unregister_chrdev(drv_data->major, "si4702"); + device_remove_file(&client->dev, &si4702_dev_attr); +gpio_put: + si4702_gpio_put(); +disable_vdd: + if (plat_data->reg_vdd) { + regulator_disable(drv_data->vdd); + regulator_put(drv_data->vdd); + } +disable_vio: + if (plat_data->reg_vio) { + regulator_disable(drv_data->vio); + regulator_put(drv_data->vio); + } + +free_drv_data: + kfree(drv_data); + + return -ENODEV; +} + +static int __devexit si4702_remove(struct i2c_client *client) +{ + struct mxc_fm_platform_data *plat_data; + struct si4702_drvdata *drv_data = dev_get_drvdata(&client->dev); + + plat_data = (struct mxc_fm_platform_data *)client->dev.platform_data; + + device_destroy(drv_data->radio_class, + MKDEV(drv_data->major, drv_data->minor)); + class_destroy(drv_data->radio_class); + + unregister_chrdev(drv_data->major, "si4702"); + device_remove_file(&client->dev, &si4702_dev_attr); + si4702_gpio_put(); + + if (plat_data->reg_vdd) + regulator_put(drv_data->vdd); + + if (plat_data->reg_vio) + regulator_put(drv_data->vio); + + kfree(si4702_drvdata); + si4702_drvdata = NULL; + + return 0; +} + +static const struct i2c_device_id si4702_id[] = { + {"si4702", 0}, + {}, +}; + +MODULE_DEVICE_TABLE(i2c, si4702_id); + +static struct i2c_driver i2c_si4702_driver = { + .driver = { + .name = "si4702", + }, + .probe = si4702_probe, + .remove = si4702_remove, + .suspend = si4702_suspend, + .resume = si4702_resume, + .id_table = si4702_id, +}; + +static int __init init_si4702(void) +{ + /*add to i2c driver */ + pr_info("add si4702 i2c driver\n"); + return i2c_add_driver(&i2c_si4702_driver); +} + +static void __exit exit_si4702(void) +{ + i2c_del_driver(&i2c_si4702_driver); +} + +module_init(init_si4702); +module_exit(exit_si4702); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("SI4702 FM driver"); +MODULE_LICENSE("GPL"); |