/* arch/arm/mach-msm/board-supersonic-microp.c * Copyright (C) 2009 HTC Corporation. * * This software is licensed under the terms of the GNU General Public * License version 2, as published by the Free Software Foundation, and * may be copied, distributed, and modified under those terms. * * 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. */ #ifdef CONFIG_MICROP_COMMON #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "board-supersonic.h" #define INT_PSENSOR (1<<4) static int misc_opened; static unsigned int als_power_control; static struct mutex als_power_lock; static void p_sensor_do_work(struct work_struct *w); static DECLARE_WORK(p_sensor_work, p_sensor_do_work); struct wake_lock proximity_wake_lock; static struct capella_cm3602_data { struct input_dev *input_dev; struct capella_cm3602_platform_data *pdata; int enabled; struct workqueue_struct *p_sensor_wq; } the_data; static int psensor_intr_enable(uint8_t enable) { int ret; uint8_t addr, data[2]; if (enable) addr = MICROP_I2C_WCMD_GPI_INT_CTL_EN; else addr = MICROP_I2C_WCMD_GPI_INT_CTL_DIS; data[0] = INT_PSENSOR >> 8; data[1] = INT_PSENSOR & 0xFF; ret = microp_i2c_write(addr, data, 2); if (ret < 0) pr_err("%s: %s p-sensor interrupt failed\n", __func__, (enable ? "enable" : "disable")); return ret; } static int supersonic_microp_function_init(struct i2c_client *client) { struct microp_i2c_platform_data *pdata; struct microp_i2c_client_data *cdata; uint8_t data[20]; int i, j; int ret; pdata = client->dev.platform_data; cdata = i2c_get_clientdata(client); /* Headset remote key */ ret = microp_function_check(client, MICROP_FUNCTION_REMOTEKEY); if (ret >= 0) { i = ret; pdata->function_node[MICROP_FUNCTION_REMOTEKEY] = i; cdata->int_pin.int_remotekey = pdata->microp_function[i].int_pin; for (j = 0; j < 6; j++) { data[j] = (uint8_t)(pdata->microp_function[i].levels[j] >> 8); data[j + 6] = (uint8_t)(pdata->microp_function[i].levels[j]); } ret = microp_i2c_write(MICROP_I2C_WCMD_REMOTEKEY_TABLE, data, 12); if (ret) goto exit; } /* Reset button interrupt */ ret = microp_write_interrupt(client, (1<<8), 1); if (ret) goto exit; /* HDMI interrupt */ ret = microp_write_interrupt(client, (1 << 1), 1); if (ret) goto exit; return 0; exit: return ret; } static int report_psensor_data(void) { int ret, ps_data = 0; uint8_t data[3] = {0, 0, 0}; ret = microp_i2c_read(MICROP_I2C_RCMD_GPIO_STATUS, data, 3); if (ret < 0) pr_err("%s: read data failed\n", __func__); else { ps_data = (data[2] & 0x10) ? 1 : 0; pr_info("proximity %s\n", ps_data ? "FAR" : "NEAR"); /* 0 is close, 1 is far */ input_report_abs(the_data.input_dev, ABS_DISTANCE, ps_data); input_sync(the_data.input_dev); wake_lock_timeout(&proximity_wake_lock, 2*HZ); } return ret; } static int capella_cm3602_enable(struct capella_cm3602_data *data) { int rc; pr_info("%s\n", __func__); if (data->enabled) { pr_info("%s: already enabled\n", __func__); return 0; } /* dummy report */ input_report_abs(data->input_dev, ABS_DISTANCE, -1); input_sync(data->input_dev); rc = data->pdata->power(PS_PWR_ON, 1); if (rc < 0) return -EIO; rc = gpio_direction_output(data->pdata->p_en, 0); if (rc < 0) { pr_err("%s: set psesnor enable failed!!", __func__); return -EIO; } msleep(220); rc = psensor_intr_enable(1); if (rc < 0) return -EIO; data->enabled = 1; report_psensor_data(); return rc; } static int capella_cm3602_disable(struct capella_cm3602_data *data) { int rc = -EIO; pr_info("%s\n", __func__); if (!data->enabled) { pr_info("%s: already disabled\n", __func__); return 0; } rc = psensor_intr_enable(0); if (rc < 0) return -EIO; rc = gpio_direction_output(data->pdata->p_en, 1); if (rc < 0) { pr_err("%s: set GPIO failed!!", __func__); return -EIO; } rc = data->pdata->power(PS_PWR_ON, 0); if (rc < 0) return -EIO; data->enabled = 0; return rc; } static ssize_t capella_cm3602_show(struct device *dev, struct device_attribute *attr, char *buf) { int ret; ret = sprintf(buf, "proximity enabled = %d\n", the_data.enabled); return ret; } static ssize_t capella_cm3602_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count ) { ssize_t val; val = -1; sscanf(buf, "%u", &val); if (val < 0 || val > 1) return -EINVAL; /* Enable capella_cm3602*/ if (val == 1) capella_cm3602_enable(&the_data); /* Disable capella_cm3602*/ if (val == 0) capella_cm3602_disable(&the_data); return count; } static DEVICE_ATTR(proximity, 0644, capella_cm3602_show, capella_cm3602_store); static int capella_cm3602_open(struct inode *inode, struct file *file) { pr_info("%s\n", __func__); if (misc_opened) return -EBUSY; misc_opened = 1; return 0; } static int capella_cm3602_release(struct inode *inode, struct file *file) { pr_info("%s\n", __func__); misc_opened = 0; return capella_cm3602_disable(&the_data); } static long capella_cm3602_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { int val; pr_info("%s cmd %d\n", __func__, _IOC_NR(cmd)); switch (cmd) { case CAPELLA_CM3602_IOCTL_ENABLE: if (get_user(val, (unsigned long __user *)arg)) return -EFAULT; if (val) return capella_cm3602_enable(&the_data); else return capella_cm3602_disable(&the_data); break; case CAPELLA_CM3602_IOCTL_GET_ENABLED: return put_user(the_data.enabled, (unsigned long __user *)arg); break; default: pr_err("%s: invalid cmd %d\n", __func__, _IOC_NR(cmd)); return -EINVAL; } } static void p_sensor_do_work(struct work_struct *w) { report_psensor_data(); } static irqreturn_t p_sensor_irq_handler(int irq, void *data) { struct capella_cm3602_data *ip = data; queue_work(ip->p_sensor_wq, &p_sensor_work); return IRQ_HANDLED; } static struct file_operations capella_cm3602_fops = { .owner = THIS_MODULE, .open = capella_cm3602_open, .release = capella_cm3602_release, .unlocked_ioctl = capella_cm3602_ioctl }; static struct miscdevice capella_cm3602_misc = { .minor = MISC_DYNAMIC_MINOR, .name = "cm3602", .fops = &capella_cm3602_fops }; static int capella_cm3602_probe(struct platform_device *pdev) { int rc = -1; struct input_dev *input_dev; struct capella_cm3602_data *ip; struct capella_cm3602_platform_data *pdata; struct class *proximity_attr_class; struct device *proximity_attr_dev; pr_info("%s: probe\n", __func__); pdata = dev_get_platdata(&pdev->dev); ip = &the_data; platform_set_drvdata(pdev, ip); input_dev = input_allocate_device(); if (!input_dev) { pr_err("%s: could not allocate input device\n", __func__); rc = -ENOMEM; goto done; } ip->input_dev = input_dev; ip->pdata = pdata; input_set_drvdata(input_dev, ip); input_dev->name = "proximity"; set_bit(EV_ABS, input_dev->evbit); input_set_abs_params(input_dev, ABS_DISTANCE, 0, 1, 0, 0); rc = input_register_device(input_dev); if (rc < 0) { pr_err("%s: could not register input device\n", __func__); goto err_free_input_device; } rc = misc_register(&capella_cm3602_misc); if (rc < 0) { pr_err("%s: could not register misc device\n", __func__); goto err_unregister_input_device; } wake_lock_init(&proximity_wake_lock, WAKE_LOCK_SUSPEND, "proximity"); proximity_attr_class = class_create(THIS_MODULE, "sensors"); if (IS_ERR(proximity_attr_class)) { pr_err("%s: class_create failed\n", __func__); rc = PTR_ERR(proximity_attr_class); proximity_attr_class = NULL; goto err_create_class; } proximity_attr_dev = device_create(proximity_attr_class, NULL, 0, "%s", "proximity_sensor"); if (unlikely(IS_ERR(proximity_attr_dev))) { pr_err("%s: device create failed\n", __func__); rc = PTR_ERR(proximity_attr_dev); proximity_attr_dev = NULL; goto err_create_proximity_attr_device; } rc = device_create_file(proximity_attr_dev, &dev_attr_proximity); if (rc) { pr_err("%s: device_create_file failed\n", __func__); goto err_create_proximity_device_file; } ip->p_sensor_wq = create_workqueue("p-sensor_microp_wq"); if (ip->p_sensor_wq == NULL) { pr_err("%s: create_workqueue failed\n", __func__); goto err_create_workqueue; } rc = gpio_request(pdata->p_en, "gpio_proximity_en"); if (rc < 0) { pr_err("%s: gpio %d request failed (%d)\n", __func__, pdata->p_en, rc); goto err_request_proximity_en; } rc = request_irq(pdata->p_out, p_sensor_irq_handler, IRQF_TRIGGER_NONE, "p-sensor_microp", ip); if (rc < 0) { pr_err("%s: request_irq(%d) failed for (%d)\n", __func__, pdata->p_out, rc); goto err_request_proximity_irq; } goto done; err_request_proximity_irq: gpio_free(pdata->p_en); err_request_proximity_en: destroy_workqueue(ip->p_sensor_wq); err_create_workqueue: device_remove_file(proximity_attr_dev, &dev_attr_proximity); err_create_proximity_device_file: device_unregister(proximity_attr_dev); err_create_proximity_attr_device: class_destroy(proximity_attr_class); err_create_class: misc_deregister(&capella_cm3602_misc); err_unregister_input_device: input_unregister_device(input_dev); err_free_input_device: input_free_device(input_dev); done: return rc; } static struct microp_ops ops = { .init_microp_func = supersonic_microp_function_init, }; void __init supersonic_microp_init(void) { microp_register_ops(&ops); } static struct platform_driver capella_cm3602_driver = { .probe = capella_cm3602_probe, .driver = { .name = "supersonic_proximity", .owner = THIS_MODULE }, }; static int __init supersonic_capella_cm3602_init(void) { if (!machine_is_supersonic()) return 0; return platform_driver_register(&capella_cm3602_driver); } device_initcall(supersonic_capella_cm3602_init); #endif