| /* |
| * Copyright (C) 2012-2013 Motorola Mobility LLC |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License version 2 as |
| * published by the Free Software Foundation. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| * |
| * You should have received a copy of the GNU General Public License |
| * along with this program; if not, write to the Free Software |
| * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA |
| * 02111-1307, USA |
| */ |
| #include <linux/clk.h> |
| #include <linux/debugfs.h> |
| #include <linux/delay.h> |
| #include <linux/err.h> |
| #include <linux/errno.h> |
| #include <linux/export.h> |
| #include <linux/gpio.h> |
| #include <linux/input.h> |
| #include <linux/i2c.h> |
| #include <linux/io.h> |
| #include <linux/wakelock.h> |
| #include <linux/module.h> |
| #include <linux/moduleparam.h> |
| #include <linux/of.h> |
| #include <linux/of_gpio.h> |
| #include <linux/miscdevice.h> |
| #include <linux/regulator/consumer.h> |
| #include <linux/seq_file.h> |
| #include <linux/slab.h> |
| #include <linux/string.h> |
| #include <linux/uaccess.h> |
| #include <linux/console.h> |
| #include <linux/fsa8500.h> |
| #include <sound/soc.h> |
| #include <sound/jack.h> |
| |
| #include "fsa8500-core.h" |
| |
| |
| #define NAME "fsa8500" |
| |
| #define I2C_RETRY_DELAY 20 /* ms */ |
| #define I2C_RETRIES 5 |
| |
| #define FSA8500_JACK_MASK (SND_JACK_HEADSET | SND_JACK_HEADPHONE| \ |
| SND_JACK_LINEOUT | SND_JACK_UNSUPPORTED) |
| #define FSA8500_JACK_BUTTON_MASK (SND_JACK_BTN_0 | SND_JACK_BTN_1 | \ |
| SND_JACK_BTN_2 | SND_JACK_BTN_3 | \ |
| SND_JACK_BTN_4 | SND_JACK_BTN_5 | \ |
| SND_JACK_BTN_6 | SND_JACK_BTN_7) |
| |
| #define SND_JACK_BTN_SHIFT 20 |
| #define FSA8500_LINT_DEBOUNCE 200 |
| #define FSA8500_DETECT_DEBOUNCE 2000 |
| |
| static struct snd_soc_jack hs_jack; |
| static struct snd_soc_jack button_jack; |
| |
| static struct i2c_client *fsa8500_client; |
| |
| struct fsa8500_data { |
| struct regulator *vdd; |
| int gpio_irq; |
| int gpio; |
| struct snd_soc_jack *hs_jack; |
| struct snd_soc_jack *button_jack; |
| u8 irq_status[5]; |
| int hs_acc_type; |
| int inserted; |
| int button_pressed; |
| int amp_state; |
| int mic_state; |
| int alwayson_micb; |
| u32 detect_time_ms; |
| struct mutex lock; |
| struct wake_lock wake_lock; |
| struct delayed_work work_det; |
| struct delayed_work work_restore; |
| struct workqueue_struct *wq; |
| }; |
| |
| /* I2C Read/Write Functions */ |
| static int fsa8500_i2c_read(struct i2c_client *client, |
| u8 reg, u8 *value, int len) |
| { |
| int err; |
| int tries = 0; |
| |
| struct i2c_msg msgs[] = { |
| { |
| .addr = client->addr, |
| .flags = 0, |
| .len = 1, |
| .buf = ®, |
| }, |
| { |
| .addr = client->addr, |
| .flags = I2C_M_RD, |
| .len = len, |
| .buf = value, |
| }, |
| }; |
| |
| do { |
| err = i2c_transfer(client->adapter, msgs, |
| ARRAY_SIZE(msgs)); |
| if (err != ARRAY_SIZE(msgs)) |
| msleep_interruptible(I2C_RETRY_DELAY); |
| |
| } while ((err != ARRAY_SIZE(msgs)) && (++tries < I2C_RETRIES)); |
| |
| if (err != ARRAY_SIZE(msgs)) { |
| dev_err(&client->dev, "read transfer error %d\n", err); |
| err = -EIO; |
| } else { |
| err = 0; |
| } |
| |
| return err; |
| } |
| static int fsa8500_i2c_write(struct i2c_client *client, u8 reg, u8 value) |
| { |
| int err; |
| int tries = 0; |
| u8 buf[2] = {0, 0}; |
| |
| struct i2c_msg msgs[] = { |
| { |
| .addr = client->addr, |
| .flags = client->flags & I2C_M_TEN, |
| .len = 2, |
| .buf = buf, |
| }, |
| }; |
| |
| buf[0] = reg; |
| buf[1] = value; |
| |
| do { |
| err = i2c_transfer(client->adapter, msgs, |
| ARRAY_SIZE(msgs)); |
| if (err != ARRAY_SIZE(msgs)) |
| msleep_interruptible(I2C_RETRY_DELAY); |
| } while ((err != ARRAY_SIZE(msgs)) && (++tries < I2C_RETRIES)); |
| |
| if (err != ARRAY_SIZE(msgs)) { |
| dev_err(&client->dev, "write transfer error\n"); |
| err = -EIO; |
| } else { |
| err = 0; |
| } |
| |
| return err; |
| } |
| |
| |
| static int fsa8500_reg_read(struct i2c_client *client, u8 reg, u8 *value) |
| { |
| return fsa8500_i2c_read(client, reg , value, 1); |
| } |
| |
| static int fsa8500_reg_write(struct i2c_client *client, |
| u8 reg, |
| u8 value, |
| u8 mask) |
| { |
| int retval; |
| u8 old_value; |
| |
| value &= mask; |
| |
| retval = fsa8500_reg_read(client, reg , &old_value); |
| |
| if (retval != 0) |
| goto error; |
| |
| old_value &= ~mask; |
| value |= old_value; |
| |
| retval = fsa8500_i2c_write(client, reg, value); |
| |
| error: |
| return retval; |
| } |
| |
| static int fsa8500_check_console(void) |
| { |
| struct console *con; |
| for_each_console(con) |
| if (!strncmp(con->name, "tty", 3)) |
| return 1; |
| return 0; |
| } |
| |
| static int fsa8500_initialize(struct fsa8500_platform_data *pdata, |
| struct fsa8500_data *fsa8500) |
| { |
| int i; |
| int retval; |
| |
| /* Reset */ |
| fsa8500_reg_write(fsa8500_client, FSA8500_RESET_CONTROL, |
| FSA8500_RESET, 0xff); |
| |
| /* Initialize device registers */ |
| for (i = 0; i < pdata->init_regs_num; i++) { |
| retval = fsa8500_reg_write(fsa8500_client, |
| pdata->init_regs[i].reg, |
| pdata->init_regs[i].value, 0xff); |
| if (retval != 0) |
| goto error; |
| } |
| /* Disable UART detection if console is not configured |
| to help with TTY detection */ |
| |
| if (!fsa8500_check_console()) { |
| pr_debug("%s: Console isn't set. Disable UART detection.\n", |
| __func__); |
| fsa8500_reg_write(fsa8500_client, FSA8500_CONTROL2, |
| FSA8500_UART_OFF, FSA8500_UART_OFF); |
| } |
| |
| pr_info("fsa8500_initialize success\n"); |
| return 0; |
| error: |
| pr_err("fsa8500_initialize error\n"); |
| return -EIO; |
| } |
| |
| #ifdef CONFIG_DEBUG_FS |
| |
| static struct dentry *fsa8500_dir; |
| static struct dentry *fsa8500_reg_file; |
| static struct dentry *fsa8500_set_reg_file; |
| |
| static int fsa8500_registers_print(struct seq_file *s, void *p) |
| { |
| u8 reg; |
| u8 value; |
| pr_info("%s: print registers", __func__); |
| seq_puts(s, "fsa8500 registers:\n"); |
| for (reg = 1; reg < FSA8500_MAX_REGISTER_VAL; reg++) { |
| fsa8500_reg_read(fsa8500_client, reg, &value); |
| seq_printf(s, "[0x%x]: 0x%x\n", reg, value); |
| } |
| |
| return 0; |
| } |
| |
| static int fsa8500_registers_open(struct inode *inode, struct file *file) |
| { |
| return single_open(file, fsa8500_registers_print, inode->i_private); |
| } |
| |
| |
| static int fsa8500_set_reg_open_file(struct inode *inode, struct file *file) |
| { |
| file->private_data = inode->i_private; |
| return 0; |
| } |
| |
| static ssize_t fsa8500_set_reg(struct file *file, |
| const char __user *user_buf, |
| size_t count, loff_t *ppos) |
| { |
| char buf[32]; |
| ssize_t buf_size; |
| unsigned int user_reg; |
| unsigned int user_value; |
| u8 reg_read_back; |
| /* Get userspace string and assure termination */ |
| buf_size = min(count, (sizeof(buf)-1)); |
| if (copy_from_user(buf, user_buf, buf_size)) |
| return -EFAULT; |
| buf[buf_size] = 0; |
| |
| if (sscanf(buf, "0x%02x 0x%02x", &user_reg, &user_value) != 2) |
| return -EFAULT; |
| |
| if (user_reg > FSA8500_MAX_REGISTER_VAL) { |
| pr_info("%s: val check failed", __func__); |
| return -EINVAL; |
| } |
| |
| pr_info("%s: set register 0x%02x value 0x%02x", __func__, |
| user_reg, user_value); |
| |
| fsa8500_reg_write(fsa8500_client, user_reg & 0xFF, |
| user_value & 0XFF, 0xFF); |
| fsa8500_reg_read(fsa8500_client, user_reg, ®_read_back); |
| |
| pr_info("%s: debug write reg[0x%02x] with 0x%02x after readback: 0x%02x\n", |
| __func__, user_reg, user_value, reg_read_back); |
| |
| return buf_size; |
| } |
| |
| |
| static const struct file_operations fsa8500_registers_fops = { |
| .open = fsa8500_registers_open, |
| .read = seq_read, |
| .llseek = seq_lseek, |
| .release = single_release, |
| .owner = THIS_MODULE, |
| }; |
| |
| static const struct file_operations fsa8500_get_set_reg_fops = { |
| .open = fsa8500_set_reg_open_file, |
| .write = fsa8500_set_reg, |
| .owner = THIS_MODULE, |
| }; |
| |
| |
| static void fsa8500_setup_debugfs(struct fsa8500_data *fsa8500) |
| { |
| if (!fsa8500) |
| goto exit_no_debugfs; |
| |
| fsa8500_dir = debugfs_create_dir("fsa8500", NULL); |
| if (!fsa8500_dir) |
| goto exit_no_debugfs; |
| |
| fsa8500_reg_file = debugfs_create_file("registers", |
| S_IRUGO, fsa8500_dir, fsa8500, |
| &fsa8500_registers_fops); |
| if (!fsa8500_reg_file) |
| goto exit_destroy_dir; |
| |
| fsa8500_set_reg_file = debugfs_create_file("set_reg", |
| S_IWUSR, fsa8500_dir, fsa8500, |
| &fsa8500_get_set_reg_fops); |
| if (!fsa8500_set_reg_file) |
| goto exit_destroy_set_reg; |
| |
| return; |
| |
| exit_destroy_set_reg: |
| debugfs_remove(fsa8500_reg_file); |
| exit_destroy_dir: |
| debugfs_remove(fsa8500_dir); |
| exit_no_debugfs: |
| |
| return; |
| } |
| static inline void fsa8500_remove_debugfs(void) |
| { |
| debugfs_remove(fsa8500_set_reg_file); |
| debugfs_remove(fsa8500_reg_file); |
| debugfs_remove(fsa8500_dir); |
| } |
| #else |
| static inline void fsa8500_setup_debugfs(struct fsa8500_data *fsa8500) |
| { |
| } |
| static inline void fsa8500_remove_debugfs(void) |
| { |
| } |
| #endif |
| |
| void fsa8500_hp_event(int event) |
| { |
| struct fsa8500_data *fsa8500; |
| |
| pr_debug("%s: hp_event %d\n", __func__, event); |
| |
| if (fsa8500_client == NULL) |
| return; |
| else { |
| fsa8500 = i2c_get_clientdata(fsa8500_client); |
| if (fsa8500 == NULL) |
| return; |
| } |
| |
| mutex_lock(&fsa8500->lock); |
| if (event) { |
| if (fsa8500->inserted) |
| fsa8500->amp_state = FSA8500_AMP_ENABLED; |
| } else |
| fsa8500->amp_state = FSA8500_AMP_DISABLED; |
| |
| mutex_unlock(&fsa8500->lock); |
| return; |
| } |
| EXPORT_SYMBOL_GPL(fsa8500_hp_event); |
| |
| void fsa8500_mic_event(int event) |
| { |
| struct fsa8500_data *fsa8500; |
| |
| pr_debug("%s: mic_event %d\n", __func__, event); |
| |
| if (fsa8500_client == NULL) |
| return; |
| else { |
| fsa8500 = i2c_get_clientdata(fsa8500_client); |
| if (fsa8500 == NULL) |
| return; |
| } |
| |
| mutex_lock(&fsa8500->lock); |
| if (fsa8500->alwayson_micb) { |
| mutex_unlock(&fsa8500->lock); |
| /* mic bias always on in this case nothing to do */ |
| return; |
| } |
| |
| if (event) { |
| if (fsa8500->inserted) |
| fsa8500->mic_state = FSA8500_MIC_ENABLED; |
| } else |
| fsa8500->mic_state = FSA8500_MIC_DISABLED; |
| |
| mutex_unlock(&fsa8500->lock); |
| return; |
| } |
| EXPORT_SYMBOL_GPL(fsa8500_mic_event); |
| |
| static int fsa8500_update_device_status(struct fsa8500_data *fsa8500) |
| { |
| int err; |
| |
| err = fsa8500_i2c_read(fsa8500_client, FSA8500_INT_REG1, |
| fsa8500->irq_status, sizeof(fsa8500->irq_status)); |
| if (err == -EIO) |
| return err; |
| |
| pr_debug("%s: regs 0x%x,0x%x,0x%x,0x%x,0x%x\n", __func__, |
| fsa8500->irq_status[0], fsa8500->irq_status[1], |
| fsa8500->irq_status[2], fsa8500->irq_status[3], |
| fsa8500->irq_status[4]); |
| if (fsa8500->irq_status[0]) |
| pr_debug("fsa8500:Detection event %s%s%s%s%s%s%s%s\n", |
| (fsa8500->irq_status[0] & 0x1) ? "-3pole-" : "", |
| (fsa8500->irq_status[0] & 0x2) ? "-4pole-OMTP-" : "", |
| (fsa8500->irq_status[0] & 0x4) ? "-4pole-CTIA-" : "", |
| (fsa8500->irq_status[0] & 0x8) ? "-UART-" : "", |
| (fsa8500->irq_status[0] & 0x10) ? "-Disconnect-" : "", |
| (fsa8500->irq_status[0] & 0x20) ? "-Lint-" : "", |
| (fsa8500->irq_status[0] & 0x40) ? "-Moisture-" : "", |
| (fsa8500->irq_status[0] & 0x80) ? "-Unknown-" : ""); |
| if (fsa8500->irq_status[1]) |
| pr_debug("%s: Key Short press & release: 0x%x\n", __func__, |
| fsa8500->irq_status[1]); |
| if (fsa8500->irq_status[2]) |
| pr_debug("%s: Key Long press: 0x%x\n", __func__, |
| fsa8500->irq_status[2]); |
| if (fsa8500->irq_status[3]) |
| pr_debug("%s: Key Long release: 0x%x\n", __func__, |
| fsa8500->irq_status[3]); |
| |
| return 0; |
| } |
| |
| static int fsa8500_get_hs_acc_type(struct fsa8500_data *fsa8500) |
| { |
| int acc_type; |
| if (fsa8500->irq_status[0] & 0x6) |
| acc_type = SND_JACK_HEADSET; |
| else |
| if (fsa8500->irq_status[4] & FSA8500_ACCESSORY_3) |
| acc_type = SND_JACK_LINEOUT; |
| else |
| acc_type = SND_JACK_HEADPHONE; |
| |
| pr_debug("%s: Accessory type %d\n", __func__, |
| fsa8500->irq_status[4] & 0x7); |
| return acc_type; |
| } |
| |
| static int fsa8500_report_hs(struct fsa8500_data *fsa8500) |
| { |
| unsigned int status; |
| if ((fsa8500->button_jack == NULL) || (fsa8500->hs_jack == NULL)) { |
| pr_err("fsa8500: Something plugged in, report it later\n"); |
| return -EINVAL; |
| } |
| |
| /* 3 or 4 pole HS */ |
| if (fsa8500->irq_status[0] & 0x7) { |
| fsa8500->inserted = 1; |
| fsa8500->hs_acc_type = fsa8500_get_hs_acc_type(fsa8500); |
| pr_debug("%s:report HS insert,type %d\n", __func__, |
| fsa8500->hs_acc_type); |
| snd_soc_jack_report_no_dapm(fsa8500->hs_jack, |
| fsa8500->hs_acc_type, |
| fsa8500->hs_jack->jack->type); |
| fsa8500->detect_time_ms = ktime_to_ms(ktime_get()); |
| } |
| |
| /* Disconnect or UART */ |
| if ((fsa8500->irq_status[0] & 0x18) && fsa8500->inserted) { |
| pr_debug("%s:report HS removal,type %d\n", __func__, |
| fsa8500->hs_acc_type); |
| snd_soc_jack_report_no_dapm(fsa8500->hs_jack, 0, |
| fsa8500->hs_jack->jack->type); |
| if (fsa8500->button_pressed) { |
| pr_debug("%s:report button release\n", __func__); |
| snd_soc_jack_report_no_dapm(fsa8500->button_jack, |
| 0, fsa8500->button_jack->jack->type); |
| fsa8500->button_pressed = 0; |
| } |
| fsa8500->inserted = 0; |
| } |
| |
| /* UART */ |
| if (fsa8500->irq_status[0] & 0x8) |
| pr_info("%s:UART port detected\n", __func__); |
| |
| /* Key Short press */ |
| if (fsa8500->irq_status[1] & 0x7F) { |
| status = fsa8500->irq_status[1] & 0x7F; |
| pr_debug("%s:report key 0x%x short press & release\n", |
| __func__, status); |
| snd_soc_jack_report_no_dapm(fsa8500->button_jack, |
| status<<SND_JACK_BTN_SHIFT, |
| status<<SND_JACK_BTN_SHIFT); |
| /* The framework can ignore events if they came |
| to close to each other. Add small delay between |
| press and release events */ |
| usleep(10000); |
| snd_soc_jack_report_no_dapm(fsa8500->button_jack, |
| 0, status<<SND_JACK_BTN_SHIFT); |
| } |
| |
| /* Key Long press */ |
| if (fsa8500->irq_status[2] & 0x7F) { |
| if ((ktime_to_ms(ktime_get()) - fsa8500->detect_time_ms) |
| < FSA8500_DETECT_DEBOUNCE) { |
| pr_debug("%s: key event < 2 sec. Re-detect headset.\n", |
| __func__); |
| snd_soc_jack_report_no_dapm(fsa8500->hs_jack, 0, |
| fsa8500->hs_jack->jack->type); |
| fsa8500->inserted = 0; |
| fsa8500_reg_write(fsa8500_client, |
| FSA8500_RESET_CONTROL, |
| FSA8500_RESET_DETECT, |
| FSA8500_RESET_DETECT); |
| return 0; |
| } |
| |
| status = fsa8500->irq_status[2] & 0x7F; |
| pr_debug("%s:report key 0x%x long press\n", __func__, status); |
| snd_soc_jack_report_no_dapm(fsa8500->button_jack, |
| status<<SND_JACK_BTN_SHIFT, |
| status<<SND_JACK_BTN_SHIFT); |
| |
| fsa8500->button_pressed |= status; |
| } |
| |
| /* Key Long release */ |
| if (fsa8500->irq_status[3] & 0x7F) { |
| status = fsa8500->irq_status[3] & 0x7F; |
| pr_debug("%s:report key %d release\n", __func__, status); |
| snd_soc_jack_report_no_dapm(fsa8500->button_jack, |
| 0, status<<SND_JACK_BTN_SHIFT); |
| |
| fsa8500->button_pressed &= ~status; |
| } |
| |
| return 0; |
| } |
| |
| int fsa8500_hs_detect(struct snd_soc_codec *codec) |
| { |
| int ret = -EINVAL, i; |
| struct fsa8500_platform_data *pdata; |
| struct fsa8500_data *fsa8500; |
| |
| if (fsa8500_client == NULL) |
| return ret; |
| |
| fsa8500 = i2c_get_clientdata(fsa8500_client); |
| |
| if (fsa8500 == NULL) |
| return ret; |
| |
| pdata = fsa8500_client->dev.platform_data; |
| |
| if (fsa8500->hs_jack == NULL) { |
| ret = snd_soc_jack_new(codec, "Headset Jack", |
| FSA8500_JACK_MASK, &hs_jack); |
| if (ret) { |
| pr_err("%s: Failed to create new jack\n", __func__); |
| return ret; |
| } |
| fsa8500->hs_jack = &hs_jack; |
| } |
| |
| if (fsa8500->button_jack == NULL) { |
| ret = snd_soc_jack_new(codec, "Button Jack", |
| FSA8500_JACK_BUTTON_MASK, &button_jack); |
| if (ret) { |
| pr_err("Failed to create new jack\n"); |
| return ret; |
| } |
| fsa8500->button_jack = &button_jack; |
| } |
| |
| if (pdata->num_keys && pdata->keymap) { |
| for (i = 0; i < pdata->num_keys; i++) { |
| ret = snd_jack_set_key(fsa8500->button_jack->jack, |
| pdata->keymap[i].soc_btn, |
| pdata->keymap[i].keycode); |
| if (ret) { |
| pr_err("%s: Failed to set code for 0x%x -- 0x%x\n", |
| __func__, pdata->keymap[i].soc_btn, |
| pdata->keymap[i].keycode); |
| return ret; |
| } |
| } |
| } else { |
| ret = snd_jack_set_key(fsa8500->button_jack->jack, |
| SND_JACK_BTN_0, KEY_MEDIA); |
| if (ret) { |
| pr_err("%s: Failed to set code for btn-0\n", __func__); |
| return ret; |
| } |
| } |
| |
| mutex_lock(&fsa8500->lock); |
| |
| |
| /* if something is plugged in, the irq has been triggered, |
| the status regs were readed and cleared. |
| Just report stored value */ |
| |
| ret = fsa8500_report_hs(fsa8500); |
| |
| mutex_unlock(&fsa8500->lock); |
| |
| return ret; |
| } |
| EXPORT_SYMBOL_GPL(fsa8500_hs_detect); |
| |
| static irqreturn_t fsa8500_irq_handler(int irq, void *data) |
| { |
| struct fsa8500_data *irq_data = data; |
| |
| wake_lock_timeout(&irq_data->wake_lock, HZ); |
| queue_work(irq_data->wq, &irq_data->work_det.work); |
| return IRQ_HANDLED; |
| } |
| |
| static int lint_counter; |
| static void fsa8500_det_thread(struct work_struct *work) |
| { |
| u8 tmp_status[5]; |
| struct fsa8500_data *irq_data = |
| i2c_get_clientdata(fsa8500_client); |
| |
| |
| mutex_lock(&irq_data->lock); |
| wake_lock(&irq_data->wake_lock); |
| |
| if (fsa8500_update_device_status(irq_data)) { |
| queue_delayed_work(irq_data->wq, &irq_data->work_det, |
| msecs_to_jiffies(2000)); |
| goto skip_report; |
| } else if ((irq_data->irq_status[0] & 0x20) && |
| (irq_data->irq_status[0] & 0x7)) { |
| /* LINT detected, delay for 200ms*/ |
| memcpy(tmp_status, irq_data->irq_status, sizeof(tmp_status)); |
| msleep(FSA8500_LINT_DEBOUNCE); |
| fsa8500_update_device_status(irq_data); |
| if (irq_data->irq_status[0] & 0x18) { |
| lint_counter++; |
| goto skip_report; |
| } else |
| memcpy(irq_data->irq_status, |
| tmp_status, sizeof(tmp_status)); |
| } |
| |
| if (irq_data->irq_status[0]) |
| lint_counter = 0; |
| fsa8500_report_hs(irq_data); |
| |
| skip_report: |
| if (lint_counter == 5) { |
| /* Disable LINT detect to avoid false detection */ |
| pr_info("%s:Too many LINT irq.Disable LINT detection\n", |
| __func__); |
| fsa8500_reg_write(fsa8500_client, FSA8500_CONTROL2, |
| FSA8500_LINT_OFF, FSA8500_LINT_OFF); |
| lint_counter = 0; |
| /* Schedule LINT mode re-enable in 5 min */ |
| queue_delayed_work(irq_data->wq, &irq_data->work_restore, |
| msecs_to_jiffies(5 * 60 * 1000)); |
| } |
| wake_unlock(&irq_data->wake_lock); |
| mutex_unlock(&irq_data->lock); |
| } |
| |
| static int lint_counter; |
| static void fsa8500_restore_thread(struct work_struct *work) |
| { |
| struct fsa8500_data *irq_data = |
| i2c_get_clientdata(fsa8500_client); |
| |
| mutex_lock(&irq_data->lock); |
| wake_lock(&irq_data->wake_lock); |
| |
| pr_info("%s:Re-enable LINT detection\n", __func__); |
| fsa8500_reg_write(fsa8500_client, FSA8500_CONTROL2, |
| 0, FSA8500_LINT_OFF); |
| lint_counter = 0; |
| wake_unlock(&irq_data->wake_lock); |
| mutex_unlock(&irq_data->lock); |
| } |
| |
| #ifdef CONFIG_OF |
| static int fsa8500_parse_dt_regs_array(const u32 *arr, |
| struct fsa8500_regs *regs, int count) |
| { |
| u32 len = 0, reg, val; |
| int i; |
| |
| if (!arr) |
| return 0; |
| |
| for (i = 0; i < count*2; i += 2) { |
| reg = be32_to_cpu(arr[i]); |
| val = be32_to_cpu(arr[i + 1]); |
| if ((reg < FSA8500_MAX_REGISTER_VAL) && (val < 0xff)) { |
| regs->reg = (char) reg; |
| regs->value = (char) val; |
| len++; |
| pr_debug("%s: regs: 0x%x=0x%x\n", __func__, reg, val); |
| regs++; |
| } |
| } |
| return len; |
| } |
| |
| static int fsa8500_parse_dt_keymap(const u32 *arr, |
| struct fsa8500_keymap *keymap, int count) |
| { |
| u32 len = 0, btn, code; |
| int i; |
| |
| if (!arr) |
| return 0; |
| |
| for (i = 0; i < count*2; i += 2) { |
| btn = be32_to_cpu(arr[i]); |
| code = be32_to_cpu(arr[i + 1]); |
| if (btn & 0xff00000) { |
| keymap->soc_btn = btn; |
| keymap->keycode = code; |
| len++; |
| pr_debug("%s: key: 0x%x=0x%x\n", __func__, btn, code); |
| keymap++; |
| } |
| } |
| return len; |
| } |
| |
| static struct fsa8500_platform_data * |
| fsa8500_of_init(struct i2c_client *client) |
| { |
| struct fsa8500_platform_data *pdata; |
| struct device_node *np = client->dev.of_node; |
| const u32 *regs_arr, *keymap; |
| int regs_len, keymap_len; |
| pdata = devm_kzalloc(&client->dev, sizeof(*pdata), GFP_KERNEL); |
| if (!pdata) { |
| dev_err(&client->dev, "pdata allocation failure\n"); |
| return NULL; |
| } |
| |
| of_property_read_u32(np, "fsa8500-always-on-micbias", |
| &pdata->alwayson_micbias); |
| pdata->irq_gpio = of_get_gpio(np, 0); |
| |
| regs_arr = of_get_property(np, "fsa8500-init-regs", |
| ®s_len); |
| if (!regs_arr || (regs_len & 1)) { |
| pr_warn("fsa8500: No init registers setting\n"); |
| regs_len = 0; |
| } else { |
| regs_len /= 2 * sizeof(u32); |
| if (regs_len > FSA8500_MAX_REGISTER_VAL) |
| regs_len = FSA8500_MAX_REGISTER_VAL; |
| |
| pdata->init_regs = devm_kzalloc(&client->dev, |
| sizeof(struct fsa8500_regs) * regs_len, |
| GFP_KERNEL); |
| |
| if (!pdata->init_regs) { |
| pr_warn("fsa8500: init_regs allocation failure\n"); |
| return pdata; |
| } |
| pdata->init_regs_num = fsa8500_parse_dt_regs_array(regs_arr, |
| pdata->init_regs, regs_len); |
| } |
| |
| |
| keymap = of_get_property(np, "fsa8500-keymap", |
| &keymap_len); |
| if (!keymap || (keymap_len & 1)) { |
| pr_warn("fsa8500: No keymap setting\n"); |
| keymap_len = 0; |
| } else { |
| keymap_len /= 2 * sizeof(u32); |
| if (keymap_len > FSA8500_NUM_KEYS) |
| keymap_len = FSA8500_NUM_KEYS; |
| |
| pdata->keymap = devm_kzalloc(&client->dev, |
| sizeof(struct fsa8500_keymap) * keymap_len, |
| GFP_KERNEL); |
| if (!pdata->keymap) { |
| pr_warn("fsa8500: keymap allocation failure\n"); |
| return pdata; |
| } |
| pdata->num_keys = fsa8500_parse_dt_keymap(keymap, |
| pdata->keymap, keymap_len); |
| } |
| |
| return pdata; |
| } |
| #else |
| static inline struct fsa8500_platform_data * |
| fsa8500_of_init(struct i2c_client *client) |
| { |
| return NULL; |
| } |
| #endif |
| |
| static int fsa8500_probe(struct i2c_client *client, |
| const struct i2c_device_id *id) |
| { |
| struct fsa8500_data *fsa8500; |
| struct fsa8500_platform_data *fsa8500_pdata; |
| struct regulator *vdd; |
| u8 device_id; |
| int err; |
| |
| if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { |
| dev_err(&client->dev, "check_functionality failed\n"); |
| return -EIO; |
| } |
| |
| /* enable regulators */ |
| vdd = regulator_get(&client->dev, "hs_det_vdd"); |
| if (IS_ERR(vdd)) |
| pr_warn("%s: Can't get vdd regulator.\n", __func__); |
| else { |
| err = regulator_enable(vdd); |
| if (err < 0) { |
| pr_err("%s: Error enabling vdd regulator.\n", __func__); |
| goto reg_enable_vdd_fail; |
| } |
| } |
| |
| if (fsa8500_reg_read(client, FSA8500_DEVICE_ID, &device_id)) { |
| if (!IS_ERR_OR_NULL(vdd)) |
| regulator_put(vdd); |
| return -EPROBE_DEFER; |
| } else |
| dev_info(&client->dev, "Device ID is 0x%x\n", device_id>>4); |
| |
| if (client->dev.of_node) |
| client->dev.platform_data = fsa8500_of_init(client); |
| |
| fsa8500_pdata = client->dev.platform_data; |
| |
| /* Check platform data */ |
| if (fsa8500_pdata == NULL) { |
| dev_err(&client->dev, |
| "platform data is NULL\n"); |
| return -EINVAL; |
| } |
| |
| fsa8500 = kzalloc(sizeof(*fsa8500), GFP_KERNEL); |
| if (fsa8500 == NULL) { |
| dev_err(&client->dev, |
| "Failed to allocate memory for module data\n"); |
| return -ENOMEM; |
| } |
| |
| err = gpio_request(fsa8500_pdata->irq_gpio, "hs irq"); |
| if (err) { |
| dev_err(&client->dev, "fsa8500 irq gpio init failed\n"); |
| goto gpio_init_fail; |
| } |
| |
| /* set global variables */ |
| fsa8500_client = client; |
| i2c_set_clientdata(client, fsa8500); |
| mutex_init(&fsa8500->lock); |
| fsa8500->gpio = fsa8500_pdata->irq_gpio; |
| fsa8500->vdd = vdd; |
| fsa8500->inserted = 0; |
| fsa8500->button_pressed = 0; |
| fsa8500->button_jack = NULL; |
| fsa8500->hs_jack = NULL; |
| /* This flag is used to indicate that mic bias should be always |
| * on when headset is inserted, needed for always on voice |
| * operations and noise estimate. |
| */ |
| fsa8500->alwayson_micb = fsa8500_pdata->alwayson_micbias; |
| |
| |
| wake_lock_init(&fsa8500->wake_lock, WAKE_LOCK_SUSPEND, "hs_det"); |
| |
| /* Initialize device registers */ |
| |
| if (fsa8500_initialize(fsa8500_pdata, fsa8500)) { |
| err = -EIO; |
| goto wq_fail; |
| } |
| fsa8500->wq = create_singlethread_workqueue("fsa8500"); |
| if (fsa8500->wq == NULL) { |
| err = -ENOMEM; |
| goto wq_fail; |
| } |
| |
| INIT_DELAYED_WORK(&fsa8500->work_det, fsa8500_det_thread); |
| INIT_DELAYED_WORK(&fsa8500->work_restore, fsa8500_restore_thread); |
| |
| fsa8500->gpio_irq = gpio_to_irq(fsa8500->gpio); |
| /* active low interrupt */ |
| err = request_irq(fsa8500->gpio_irq, fsa8500_irq_handler, |
| IRQF_TRIGGER_FALLING, |
| "hs_det", fsa8500); |
| if (err) { |
| pr_err("%s: IRQ request failed: %d\n", __func__, err); |
| goto irq_fail; |
| } |
| enable_irq_wake(fsa8500->gpio_irq); |
| |
| /* setup debug fs interfaces */ |
| fsa8500_setup_debugfs(fsa8500); |
| |
| pr_info("fsa8500 probed successfully\n"); |
| |
| return 0; |
| |
| irq_fail: |
| wake_lock_destroy(&fsa8500->wake_lock); |
| destroy_workqueue(fsa8500->wq); |
| wq_fail: |
| gpio_free(fsa8500->gpio); |
| gpio_init_fail: |
| kfree(fsa8500); |
| fsa8500_client = NULL; |
| if (!IS_ERR_OR_NULL(vdd)) |
| regulator_disable(vdd); |
| reg_enable_vdd_fail: |
| if (!IS_ERR_OR_NULL(vdd)) |
| regulator_put(vdd); |
| return err; |
| } |
| |
| static int fsa8500_remove(struct i2c_client *client) |
| { |
| struct fsa8500_data *fsa8500 = i2c_get_clientdata(client); |
| if (!IS_ERR_OR_NULL(fsa8500->vdd)) { |
| regulator_disable(fsa8500->vdd); |
| regulator_put(fsa8500->vdd); |
| } |
| gpio_free(fsa8500->gpio); |
| wake_lock_destroy(&fsa8500->wake_lock); |
| destroy_workqueue(fsa8500->wq); |
| fsa8500_remove_debugfs(); |
| kfree(fsa8500); |
| fsa8500_client = NULL; |
| return 0; |
| } |
| |
| static const struct i2c_device_id fsa8500_id[] = { |
| {NAME, 0}, |
| {}, |
| }; |
| |
| MODULE_DEVICE_TABLE(i2c, fsa8500_id); |
| |
| #ifdef CONFIG_OF |
| static struct of_device_id fsa8500_match_tbl[] = { |
| { .compatible = "fairchild,fsa8500" }, |
| { }, |
| }; |
| MODULE_DEVICE_TABLE(of, fsa8500_match_tbl); |
| #endif |
| |
| static struct i2c_driver fsa8500_driver = { |
| .driver = { |
| .name = NAME, |
| .owner = THIS_MODULE, |
| .of_match_table = of_match_ptr(fsa8500_match_tbl), |
| }, |
| .probe = fsa8500_probe, |
| .remove = fsa8500_remove, |
| .id_table = fsa8500_id, |
| }; |
| |
| static int fsa8500_init(void) |
| { |
| return i2c_add_driver(&fsa8500_driver); |
| } |
| |
| static void fsa8500_exit(void) |
| { |
| i2c_del_driver(&fsa8500_driver); |
| return; |
| } |
| |
| module_init(fsa8500_init); |
| module_exit(fsa8500_exit); |
| |
| MODULE_DESCRIPTION("Fairchild FSA8500 driver"); |
| MODULE_LICENSE("GPL"); |
| MODULE_AUTHOR("Motorola Mobility"); |