// SPDX-License-Identifier: GPL-2.0 /* * Copyright (c) 2020 MediaTek Inc. */ /* * Author: yucong xiong * */ #define pr_fmt(fmt) " " fmt #include #include #include #include #include #include #include "cust_alsps.h" #include "cm36558.h" #include "alsps.h" #define CM36558_DEV_NAME "CM36558" #define I2C_FLAG_WRITE 0 #define I2C_FLAG_READ 1 #define DRIVER_ATTR(_name, _mode, _show, _store) \ struct driver_attribute driver_attr_##_name = \ __ATTR(_name, _mode, _show, _store) /*----------------------------------------------------------------------------*/ static int CM36558_i2c_probe(struct i2c_client *client, const struct i2c_device_id *id); static int CM36558_i2c_remove(struct i2c_client *client); static int CM36558_i2c_detect(struct i2c_client *client, struct i2c_board_info *info); static int CM36558_i2c_suspend(struct device *dev); static int CM36558_i2c_resume(struct device *dev); /*----------------------------------------------------------------------------*/ static const struct i2c_device_id CM36558_i2c_id[] = { {CM36558_DEV_NAME, 0}, {} }; static unsigned long long int_top_time; /*----------------------------------------------------------------------------*/ struct CM36558_priv { struct alsps_hw hw; struct i2c_client *client; struct work_struct eint_work; /*misc */ u16 als_modulus; atomic_t i2c_retry; atomic_t als_suspend; atomic_t als_debounce; /*debounce time after enabling als */ atomic_t als_deb_on; /*indicates if the debounce is on */ atomic_t als_deb_end; /*the jiffies representing the end of debounce */ atomic_t ps_mask; /*mask ps: always return far away */ atomic_t ps_debounce; /*debounce time after enabling ps */ atomic_t ps_deb_on; /*indicates if the debounce is on */ atomic_t ps_deb_end; /*the jiffies representing the end of debounce */ atomic_t ps_suspend; atomic_t trace; atomic_t init_done; struct device_node *irq_node; int irq; /*data */ u16 als; u16 ps; u8 _align; u16 als_level_num; u16 als_value_num; u32 als_level[C_CUST_ALS_LEVEL - 1]; u32 als_value[C_CUST_ALS_LEVEL]; int ps_cali; atomic_t als_cmd_val; /*the cmd value can't be read, stored in ram */ atomic_t ps_cmd_val; /*the cmd value can't be read, stored in ram */ atomic_t ps_thd_val_high; /*the cmd value can't be read, stored in ram */ atomic_t ps_thd_val_low; /*the cmd value can't be read, stored in ram */ atomic_t als_thd_val_high; /*cmd value can't be read, stored in ram*/ atomic_t als_thd_val_low; /*cmd value can't be read, stored in ram */ atomic_t ps_thd_val; ulong enable; /*enable mask */ ulong pending_intr; /*pending interrupt */ }; #ifdef CONFIG_OF static const struct of_device_id alsps_of_match[] = { {.compatible = "mediatek,alsps"}, {}, }; #endif #ifdef CONFIG_PM_SLEEP static const struct dev_pm_ops CM36558_pm_ops = { SET_SYSTEM_SLEEP_PM_OPS(CM36558_i2c_suspend, CM36558_i2c_resume)}; #endif static struct i2c_driver CM36558_i2c_driver = { .probe = CM36558_i2c_probe, .remove = CM36558_i2c_remove, .detect = CM36558_i2c_detect, .id_table = CM36558_i2c_id, .driver = { .name = CM36558_DEV_NAME, #ifdef CONFIG_PM_SLEEP .pm = &CM36558_pm_ops, #endif #ifdef CONFIG_OF .of_match_table = alsps_of_match, #endif }, }; struct PS_CALI_DATA_STRUCT { int close; int far_away; int valid; }; static struct i2c_client *CM36558_i2c_client; static struct CM36558_priv *CM36558_obj; static int CM36558_local_init(void); static int CM36558_remove(void); static int CM36558_init_flag = -1; static struct alsps_init_info CM36558_init_info = { .name = "CM36558", .init = CM36558_local_init, .uninit = CM36558_remove, }; static DEFINE_MUTEX(CM36558_mutex); enum { CMC_BIT_ALS = 1, CMC_BIT_PS = 2, } CMC_BIT; enum { CMC_TRC_ALS_DATA = 0x0001, CMC_TRC_PS_DATA = 0x0002, CMC_TRC_EINT = 0x0004, CMC_TRC_IOCTL = 0x0008, CMC_TRC_I2C = 0x0010, CMC_TRC_CVT_ALS = 0x0020, CMC_TRC_CVT_PS = 0x0040, CMC_TRC_CVT_AAL = 0x0080, CMC_TRC_DEBUG = 0x8000, } CMC_TRC; int CM36558_i2c_master_operate(struct i2c_client *client, char *buf, int count, int i2c_flag) { int res = 0; #ifndef CONFIG_MTK_I2C_EXTENSION struct i2c_msg msg[2]; #endif mutex_lock(&CM36558_mutex); switch (i2c_flag) { case I2C_FLAG_WRITE: #ifdef CONFIG_MTK_I2C_EXTENSION client->addr &= I2C_MASK_FLAG; res = i2c_master_send(client, buf, count); client->addr &= I2C_MASK_FLAG; #else res = i2c_master_send(client, buf, count); #endif break; case I2C_FLAG_READ: #ifdef CONFIG_MTK_I2C_EXTENSION client->addr &= I2C_MASK_FLAG; client->addr |= I2C_WR_FLAG; client->addr |= I2C_RS_FLAG; res = i2c_master_send(client, buf, (count << 8) | 1); client->addr &= I2C_MASK_FLAG; #else msg[0].addr = client->addr; msg[0].flags = 0; msg[0].len = 1; msg[0].buf = buf; msg[1].addr = client->addr; msg[1].flags = I2C_M_RD; msg[1].len = count; msg[1].buf = buf; res = i2c_transfer(client->adapter, msg, ARRAY_SIZE(msg)); #endif break; default: pr_debug("%s i2c_flag not support!\n", __func__); break; } if (res < 0) goto EXIT_ERR; mutex_unlock(&CM36558_mutex); return res; EXIT_ERR: mutex_unlock(&CM36558_mutex); pr_err("%s fail\n", __func__); return res; } /********************************************************************/ int CM36558_enable_ps(struct i2c_client *client, int enable) { struct CM36558_priv *obj = i2c_get_clientdata(client); int res = 0; u8 databuf[3]; if (enable == 1) { pr_debug("%s enable_ps\n", __func__); databuf[0] = CM36558_REG_PS_CONF1_2; res = CM36558_i2c_master_operate(client, databuf, 2, I2C_FLAG_READ); if (res < 0) { pr_err("i2c_master_send function err\n"); goto ENABLE_PS_EXIT_ERR; } pr_debug("CM36558_REG_PS_CONF1_2 valuelow = %x, high = %x\n", databuf[0], databuf[1]); databuf[2] = databuf[1]; databuf[1] = databuf[0] & 0xFE; databuf[0] = CM36558_REG_PS_CONF1_2; res = CM36558_i2c_master_operate(client, databuf, 0x3, I2C_FLAG_WRITE); if (res < 0) { pr_err("i2c_master_send function err\n"); goto ENABLE_PS_EXIT_ERR; } atomic_set(&obj->ps_deb_on, 1); atomic_set( &obj->ps_deb_end, jiffies + atomic_read(&obj->ps_debounce) / (1000 / HZ)); } else { pr_debug("%s disable_ps\n", __func__); databuf[0] = CM36558_REG_PS_CONF1_2; res = CM36558_i2c_master_operate(client, databuf, 2, I2C_FLAG_READ); if (res < 0) { pr_err("i2c_master_send function err\n"); goto ENABLE_PS_EXIT_ERR; } pr_debug("CM36558_REG_PS_CONF1_2 valuelow = %x,high = %x\n", databuf[0], databuf[1]); databuf[2] = databuf[1]; databuf[1] = databuf[0] | 0x01; databuf[0] = CM36558_REG_PS_CONF1_2; res = CM36558_i2c_master_operate(client, databuf, 0x3, I2C_FLAG_WRITE); if (res < 0) { pr_err("i2c_master_send function err\n"); goto ENABLE_PS_EXIT_ERR; } atomic_set(&obj->ps_deb_on, 0); } return 0; ENABLE_PS_EXIT_ERR: return res; } /********************************************************************/ int CM36558_enable_als(struct i2c_client *client, int enable) { struct CM36558_priv *obj = i2c_get_clientdata(client); int res = 0; u8 databuf[3]; if (enable == 1) { pr_debug("%s enable_als\n", __func__); databuf[0] = CM36558_REG_ALS_UV_CONF; res = CM36558_i2c_master_operate(client, databuf, 2, I2C_FLAG_READ); if (res < 0) { pr_err("i2c_master_send function err\n"); goto ENABLE_ALS_EXIT_ERR; } pr_debug("CM36558_REG_ALS_UV_CONF low= %x,high = %x\n", databuf[0], databuf[1]); databuf[2] = databuf[1]; databuf[1] = databuf[0] & 0xFE; databuf[0] = CM36558_REG_ALS_UV_CONF; res = CM36558_i2c_master_operate(client, databuf, 0x3, I2C_FLAG_WRITE); if (res < 0) { pr_err("i2c_master_send function err\n"); goto ENABLE_ALS_EXIT_ERR; } atomic_set(&obj->als_deb_on, 1); atomic_set(&obj->als_deb_end, jiffies + atomic_read(&obj->als_debounce) / (1000 / HZ)); } else { pr_debug("%s disable_als\n", __func__); databuf[0] = CM36558_REG_ALS_UV_CONF; res = CM36558_i2c_master_operate(client, databuf, 2, I2C_FLAG_READ); if (res < 0) { pr_err("i2c_master_send function err\n"); goto ENABLE_ALS_EXIT_ERR; } pr_debug("CM36558_REG_ALS_UV_CONF valuelow = %x, high = %x\n", databuf[0], databuf[1]); databuf[2] = databuf[1]; databuf[1] = databuf[0] | 0x01; databuf[0] = CM36558_REG_ALS_UV_CONF; res = CM36558_i2c_master_operate(client, databuf, 0x3, I2C_FLAG_WRITE); if (res < 0) { pr_err("i2c_master_send function err\n"); goto ENABLE_ALS_EXIT_ERR; } atomic_set(&obj->als_deb_on, 0); } return 0; ENABLE_ALS_EXIT_ERR: return res; } /********************************************************************/ long CM36558_read_ps(struct i2c_client *client, u16 *data) { long res = 0; u8 databuf[2]; struct CM36558_priv *obj = i2c_get_clientdata(client); databuf[0] = CM36558_REG_PS_DATA; res = CM36558_i2c_master_operate(client, databuf, 2, I2C_FLAG_READ); if (res < 0) { pr_err("i2c_master_send function err\n"); goto READ_PS_EXIT_ERR; } if (atomic_read(&obj->trace) & CMC_TRC_DEBUG) pr_debug("CM36558_REG_PS_DATA valuelow = %x,high = %x\n", databuf[0], databuf[1]); *data = ((databuf[1] << 8) | databuf[0]); if (*data < obj->ps_cali) *data = 0; else *data = *data - obj->ps_cali; return 0; READ_PS_EXIT_ERR: return res; } long CM36558_read_als(struct i2c_client *client, u16 *data) { long res = 0; u8 databuf[2]; struct CM36558_priv *obj = i2c_get_clientdata(client); databuf[0] = CM36558_REG_ALS_DATA; res = CM36558_i2c_master_operate(client, databuf, 2, I2C_FLAG_READ); if (res < 0) { pr_err("i2c_master_send function err\n"); goto READ_ALS_EXIT_ERR; } if (atomic_read(&obj->trace) & CMC_TRC_DEBUG) pr_debug("CM36558_REG_ALS_DATA value: %d\n", ((databuf[1] << 8) | databuf[0])); *data = ((databuf[1] << 8) | databuf[0]); return 0; READ_ALS_EXIT_ERR: return res; } /********************************************************************/ static int CM36558_get_ps_value(struct CM36558_priv *obj, u8 ps) { int val = 0, mask = atomic_read(&obj->ps_mask); int invalid = 0; val = 0; if (ps > atomic_read(&obj->ps_thd_val_high)) val = 0; else if (ps < atomic_read(&obj->ps_thd_val_low)) val = 1; if (atomic_read(&obj->ps_suspend)) { invalid = 1; } else if (atomic_read(&obj->ps_deb_on) == 1) { unsigned long endt = atomic_read(&obj->ps_deb_end); if (time_after(jiffies, endt)) atomic_set(&obj->ps_deb_on, 0); if (atomic_read(&obj->ps_deb_on) == 1) invalid = 1; } if (!invalid) { if (unlikely(atomic_read(&obj->trace) & CMC_TRC_CVT_PS)) { if (mask) pr_debug("PS: %05d => %05d [M]\n", ps, val); else pr_debug("PS: %05d => %05d\n", ps, val); } if (test_bit(CMC_BIT_PS, &obj->enable) == 0) { pr_debug("PS:not enable, do not report this value\n"); return -1; } else { return val; } } else { if (unlikely(atomic_read(&obj->trace) & CMC_TRC_CVT_PS)) pr_debug("PS: %05d => %05d (-1)\n", ps, val); return -1; } } static int CM36558_get_als_value(struct CM36558_priv *obj, u16 als) { int idx = 0; int invalid = 0; int level_high = 0; int level_low = 0; int level_diff = 0; int value_high = 0; int value_low = 0; int value_diff = 0; int value = 0; if ((obj->als_level_num == 0) || (obj->als_value_num == 0)) { pr_err("invalid als_level_num = %d, als_value_num = %d\n", obj->als_level_num, obj->als_value_num); return -1; } if (atomic_read(&obj->als_deb_on) == 1) { unsigned long endt = atomic_read(&obj->als_deb_end); if (time_after(jiffies, endt)) atomic_set(&obj->als_deb_on, 0); if (atomic_read(&obj->als_deb_on) == 1) invalid = 1; } for (idx = 0; idx < obj->als_level_num; idx++) { if (als < obj->hw.als_level[idx]) break; } if (idx >= obj->als_level_num || idx >= obj->als_value_num) { if (idx < obj->als_value_num) value = obj->hw.als_value[idx - 1]; else value = obj->hw.als_value[obj->als_value_num - 1]; } else { level_high = obj->hw.als_level[idx]; level_low = (idx > 0) ? obj->hw.als_level[idx - 1] : 0; level_diff = level_high - level_low; value_high = obj->hw.als_value[idx]; value_low = (idx > 0) ? obj->hw.als_value[idx - 1] : 0; value_diff = value_high - value_low; if ((level_low >= level_high) || (value_low >= value_high)) value = value_low; else value = (level_diff * value_low + (als - level_low) * value_diff + ((level_diff + 1) >> 1)) / level_diff; } if (!invalid) { if (atomic_read(&obj->trace) & CMC_TRC_CVT_AAL) pr_debug("ALS: %d [%d, %d] => %d [%d, %d]\n", als, level_low, level_high, value, value_low, value_high); } else { if (atomic_read(&obj->trace) & CMC_TRC_CVT_ALS) pr_debug("ALS: %05d => %05d (-1)\n", als, value); return -1; } return value; } static ssize_t CM36558_show_config(struct device_driver *ddri, char *buf) { ssize_t res = 0; if (!CM36558_obj) { pr_err("CM36558_obj is null!!\n"); return 0; } res = snprintf( buf, PAGE_SIZE, "(%d %d %d %d %d\n)threadhold_low=%d threadhold_high=%d\n", atomic_read(&CM36558_obj->i2c_retry), atomic_read(&CM36558_obj->als_debounce), atomic_read(&CM36558_obj->ps_mask), atomic_read(&CM36558_obj->ps_thd_val), atomic_read(&CM36558_obj->ps_debounce), atomic_read(&CM36558_obj->ps_thd_val_low), atomic_read(&CM36558_obj->ps_thd_val_high)); return res; } /*----------------------------------------------------------------------------*/ static ssize_t CM36558_store_config(struct device_driver *ddri, const char *buf, size_t count) { int retry = 0, als_deb = 0, ps_deb = 0, mask = 0, thres = 0; if (!CM36558_obj) { pr_err("CM36558_obj is null!!\n"); return 0; } if (sscanf(buf, "%d %d %d %d %d", &retry, &als_deb, &mask, &thres, &ps_deb) == 5) { atomic_set(&CM36558_obj->i2c_retry, retry); atomic_set(&CM36558_obj->als_debounce, als_deb); atomic_set(&CM36558_obj->ps_mask, mask); atomic_set(&CM36558_obj->ps_thd_val, thres); atomic_set(&CM36558_obj->ps_debounce, ps_deb); } else { pr_err("invalid content: '%s', length = %d\n", buf, (unsigned int)count); } return count; } /*----------------------------------------------------------------------------*/ static ssize_t CM36558_show_trace(struct device_driver *ddri, char *buf) { ssize_t res = 0; if (!CM36558_obj) { pr_err("CM36558_obj is null!!\n"); return 0; } res = snprintf(buf, PAGE_SIZE, "0x%04X\n", atomic_read(&CM36558_obj->trace)); return res; } /*----------------------------------------------------------------------------*/ static ssize_t CM36558_store_trace(struct device_driver *ddri, const char *buf, size_t count) { int trace = 0; if (!CM36558_obj) { pr_err("CM36558_obj is null!!\n"); return 0; } if (sscanf(buf, "0x%x", &trace) == 1) atomic_set(&CM36558_obj->trace, trace); else pr_err("invalid content: '%s', length = %d\n", buf, (unsigned int)count); return count; } /*----------------------------------------------------------------------------*/ static ssize_t CM36558_show_als(struct device_driver *ddri, char *buf) { int res = 0; if (!CM36558_obj) { pr_err("CM36558_obj is null!!\n"); return 0; } res = CM36558_read_als(CM36558_obj->client, &CM36558_obj->als); if (res) return snprintf(buf, PAGE_SIZE, "ERROR: %d\n", res); else return snprintf(buf, PAGE_SIZE, "0x%04X\n", CM36558_obj->als); } /*----------------------------------------------------------------------------*/ static ssize_t CM36558_show_ps(struct device_driver *ddri, char *buf) { ssize_t res = 0; if (!CM36558_obj) { pr_err("cm3623_obj is null!!\n"); return 0; } res = CM36558_read_ps(CM36558_obj->client, &CM36558_obj->ps); if (res) return snprintf(buf, PAGE_SIZE, "ERROR: %d\n", (unsigned int)res); else return snprintf(buf, PAGE_SIZE, "0x%04X\n", CM36558_obj->ps); } /*----------------------------------------------------------------------------*/ static ssize_t CM36558_show_reg(struct device_driver *ddri, char *buf) { u8 _bIndex = 0; u8 databuf[2] = {0}; ssize_t _tLength = 0; int res = 0; if (!CM36558_obj) { pr_err("CM36558_obj is null!!\n"); return 0; } for (_bIndex = 0; _bIndex < 0x0E; _bIndex++) { databuf[0] = _bIndex; res = CM36558_i2c_master_operate(CM36558_obj->client, databuf, 2, I2C_FLAG_READ); if (res < 0) pr_err("CM36558_i2c_master_operate err res = %d\n", res); _tLength += snprintf((buf + _tLength), (PAGE_SIZE - _tLength), "Reg[0x%02X]: 0x%04X\n", _bIndex, databuf[0] | databuf[1] << 8); } return _tLength; } /*----------------------------------------------------------------------------*/ static ssize_t CM36558_show_send(struct device_driver *ddri, char *buf) { return 0; } /*----------------------------------------------------------------------------*/ static ssize_t CM36558_store_send(struct device_driver *ddri, const char *buf, size_t count) { int addr = 0, cmd = 0; u8 dat = 0; if (!CM36558_obj) { pr_err("CM36558_obj is null!!\n"); return 0; } else if (sscanf(buf, "%x %x", &addr, &cmd) != 2) { pr_err("invalid format: '%s'\n", buf); return 0; } dat = (u8)cmd; return count; } /*----------------------------------------------------------------------------*/ static ssize_t CM36558_show_recv(struct device_driver *ddri, char *buf) { return 0; } /*----------------------------------------------------------------------------*/ static ssize_t CM36558_store_recv(struct device_driver *ddri, const char *buf, size_t count) { int addr = 0, err = 0; if (!CM36558_obj) { pr_err("CM36558_obj is null!!\n"); return 0; } err = kstrtoint(buf, 16, &addr); if (err != 0) { pr_err("invalid format: '%s'\n", buf); return 0; } return count; } /*----------------------------------------------------------------------------*/ static ssize_t CM36558_show_status(struct device_driver *ddri, char *buf) { ssize_t len = 0; if (!CM36558_obj) { pr_err("CM36558_obj is null!!\n"); return 0; } len += snprintf(buf + len, PAGE_SIZE - len, "CUST: %d, (%d %d)\n", CM36558_obj->hw.i2c_num, CM36558_obj->hw.power_id, CM36558_obj->hw.power_vol); len += snprintf(buf + len, PAGE_SIZE - len, "REGS: %02X %02X %02X %02lX %02lX\n", atomic_read(&CM36558_obj->als_cmd_val), atomic_read(&CM36558_obj->ps_cmd_val), atomic_read(&CM36558_obj->ps_thd_val), CM36558_obj->enable, CM36558_obj->pending_intr); len += snprintf(buf + len, PAGE_SIZE - len, "MISC: %d %d\n", atomic_read(&CM36558_obj->als_suspend), atomic_read(&CM36558_obj->ps_suspend)); return len; } /*----------------------------------------------------------------------------*/ #define IS_SPACE(CH) (((CH) == ' ') || ((CH) == '\n')) /*----------------------------------------------------------------------------*/ static int read_int_from_buf(struct CM36558_priv *obj, const char *buf, size_t count, u32 data[], int len) { int idx = 0, err = 0; char *cur = (char *)buf, *end = (char *)(buf + count); while (idx < len) { while ((cur < end) && IS_SPACE(*cur)) cur++; err = kstrtoint(cur, 10, &data[idx]); if (err != 0) break; idx++; while ((cur < end) && !IS_SPACE(*cur)) cur++; } return idx; } /*----------------------------------------------------------------------------*/ static ssize_t CM36558_show_alslv(struct device_driver *ddri, char *buf) { ssize_t len = 0; int idx = 0; if (!CM36558_obj) { pr_err("CM36558_obj is null!!\n"); return 0; } for (idx = 0; idx < CM36558_obj->als_level_num; idx++) len += snprintf(buf + len, PAGE_SIZE - len, "%d ", CM36558_obj->hw.als_level[idx]); len += snprintf(buf + len, PAGE_SIZE - len, "\n"); return len; } /*----------------------------------------------------------------------------*/ static ssize_t CM36558_store_alslv(struct device_driver *ddri, const char *buf, size_t count) { if (!CM36558_obj) { pr_err("CM36558_obj is null!!\n"); return 0; } else if (!strcmp(buf, "def")) { memcpy(CM36558_obj->als_level, CM36558_obj->hw.als_level, sizeof(CM36558_obj->als_level)); } else if (CM36558_obj->als_level_num != read_int_from_buf(CM36558_obj, buf, count, CM36558_obj->hw.als_level, CM36558_obj->als_level_num)) { pr_err("invalid format: '%s'\n", buf); } return count; } /*----------------------------------------------------------------------------*/ static ssize_t CM36558_show_alsval(struct device_driver *ddri, char *buf) { ssize_t len = 0; int idx = 0; if (!CM36558_obj) { pr_err("CM36558_obj is null!!\n"); return 0; } for (idx = 0; idx < CM36558_obj->als_value_num; idx++) len += snprintf(buf + len, PAGE_SIZE - len, "%d ", CM36558_obj->hw.als_value[idx]); len += snprintf(buf + len, PAGE_SIZE - len, "\n"); return len; } /*----------------------------------------------------------------------------*/ static ssize_t CM36558_store_alsval(struct device_driver *ddri, const char *buf, size_t count) { if (!CM36558_obj) { pr_err("CM36558_obj is null!!\n"); return 0; } else if (!strcmp(buf, "def")) { memcpy(CM36558_obj->als_value, CM36558_obj->hw.als_value, sizeof(CM36558_obj->als_value)); } else if (CM36558_obj->als_value_num != read_int_from_buf(CM36558_obj, buf, count, CM36558_obj->hw.als_value, CM36558_obj->als_value_num)) { pr_err("invalid format: '%s'\n", buf); } return count; } static DRIVER_ATTR(als, 0644, CM36558_show_als, NULL); static DRIVER_ATTR(ps, 0644, CM36558_show_ps, NULL); static DRIVER_ATTR(config, 0644, CM36558_show_config, CM36558_store_config); static DRIVER_ATTR(alslv, 0644, CM36558_show_alslv, CM36558_store_alslv); static DRIVER_ATTR(alsval, 0644, CM36558_show_alsval, CM36558_store_alsval); static DRIVER_ATTR(trace, 0644, CM36558_show_trace, CM36558_store_trace); static DRIVER_ATTR(status, 0644, CM36558_show_status, NULL); static DRIVER_ATTR(send, 0644, CM36558_show_send, CM36558_store_send); static DRIVER_ATTR(recv, 0644, CM36558_show_recv, CM36558_store_recv); static DRIVER_ATTR(reg, 0644, CM36558_show_reg, NULL); static struct driver_attribute *CM36558_attr_list[] = { &driver_attr_als, &driver_attr_ps, &driver_attr_trace, /*trace log */ &driver_attr_config, &driver_attr_alslv, &driver_attr_alsval, &driver_attr_status, &driver_attr_send, &driver_attr_recv, &driver_attr_reg, }; static int CM36558_create_attr(struct device_driver *driver) { int idx = 0, err = 0; int num = (int)(ARRAY_SIZE(CM36558_attr_list)); if (driver == NULL) return -EINVAL; for (idx = 0; idx < num; idx++) { err = driver_create_file(driver, CM36558_attr_list[idx]); if (err) { pr_err("driver_create_file (%s) = %d\n", CM36558_attr_list[idx]->attr.name, err); break; } } return err; } /*----------------------------------------------------------------------------*/ static int CM36558_delete_attr(struct device_driver *driver) { int idx = 0, err = 0; int num = (int)(ARRAY_SIZE(CM36558_attr_list)); if (!driver) return -EINVAL; for (idx = 0; idx < num; idx++) driver_remove_file(driver, CM36558_attr_list[idx]); return err; } static int intr_flag; /*----------------------------------------------------------------------------*/ static int CM36558_check_intr(struct i2c_client *client) { int res = 0; u8 databuf[2]; databuf[0] = CM36558_REG_PS_DATA; res = CM36558_i2c_master_operate(client, databuf, 2, I2C_FLAG_READ); if (res < 0) { pr_err("i2c_master_send function err res = %d\n", res); goto EXIT_ERR; } pr_info( "CM36558_REG_PS_DATA value value_low = %x, value_reserve = %x\n", databuf[0], databuf[1]); databuf[0] = CM36558_REG_INT_FLAG; res = CM36558_i2c_master_operate(client, databuf, 2, I2C_FLAG_READ); if (res < 0) { pr_err("i2c_master_send function err res = %d\n", res); goto EXIT_ERR; } pr_info("CM36558_REG_INT_FLAG value value_low = %x, value_high = %x\n", databuf[0], databuf[1]); if (databuf[1] & 0x02) { intr_flag = 0; } else if (databuf[1] & 0x01) { intr_flag = 1; } else { res = -1; pr_err("%s fail databuf[1]&0x01: %d\n", __func__, res); goto EXIT_ERR; } return 0; EXIT_ERR: pr_err("%s dev: %d\n", __func__, res); return res; } static void CM36558_eint_work(struct work_struct *work) { struct CM36558_priv *obj = (struct CM36558_priv *)container_of(work, struct CM36558_priv, eint_work); int res = 0; pr_info("CM36558 int top half time = %lld\n", int_top_time); res = CM36558_check_intr(obj->client); if (res != 0) { goto EXIT_INTR_ERR; } else { pr_debug("CM36558 interrupt value = %d\n", intr_flag); res = ps_report_interrupt_data(intr_flag); } #if defined(CONFIG_OF) enable_irq(obj->irq); #elif defined(CUST_EINT_ALS_TYPE) mt_eint_unmask(CUST_EINT_ALS_NUM); #else mt65xx_eint_unmask(CUST_EINT_ALS_NUM); #endif return; EXIT_INTR_ERR: #if defined(CONFIG_OF) enable_irq(obj->irq); #elif defined(CUST_EINT_ALS_TYPE) mt_eint_unmask(CUST_EINT_ALS_NUM); #else mt65xx_eint_unmask(CUST_EINT_ALS_NUM); #endif pr_err("%s err: %d\n", __func__, res); } /*----------------------------------------------------------------------------*/ static void CM36558_eint_func(void) { struct CM36558_priv *obj = CM36558_obj; if (!obj) return; int_top_time = sched_clock(); schedule_work(&obj->eint_work); } #if defined(CONFIG_OF) static irqreturn_t CM36558_eint_handler(int irq, void *desc) { CM36558_eint_func(); disable_irq_nosync(CM36558_obj->irq); return IRQ_HANDLED; } #endif /*----------------------------------------------------------------------------*/ int CM36558_setup_eint(struct i2c_client *client) { int ret; struct pinctrl *pinctrl; struct pinctrl_state *pins_default; struct pinctrl_state *pins_cfg; u32 ints[2] = {0, 0}; /* gpio setting */ pinctrl = devm_pinctrl_get(&client->dev); if (IS_ERR(pinctrl)) { ret = PTR_ERR(pinctrl); pr_err("Cannot find alsps pinctrl!\n"); return ret; } pins_default = pinctrl_lookup_state(pinctrl, "pin_default"); if (IS_ERR(pins_default)) { ret = PTR_ERR(pins_default); pr_err("Cannot find alsps pinctrl default!\n"); } pins_cfg = pinctrl_lookup_state(pinctrl, "pin_cfg"); if (IS_ERR(pins_cfg)) { ret = PTR_ERR(pins_cfg); pr_err("Cannot find alsps pinctrl pin_cfg!\n"); return ret; } pinctrl_select_state(pinctrl, pins_cfg); /* eint request */ if (CM36558_obj->irq_node) { #ifndef CONFIG_MTK_EIC /*upstream code*/ ints[0] = of_get_named_gpio(CM36558_obj->irq_node, "deb-gpios", 0); if (ints[0] < 0) { pr_err("debounce gpio not found\n"); } else{ ret = of_property_read_u32(CM36558_obj->irq_node, "debounce", &ints[1]); if (ret < 0) pr_err("debounce time not found\n"); else gpio_set_debounce(ints[0], ints[1]); pr_debug("in[0]:%d, in[1]:%d!!\n", ints[0], ints[1]); } #else ret = of_property_read_u32_array(CM36558_obj->irq_node, "debounce", ints, ARRAY_SIZE(ints)); if (ret) { pr_err("of_property_read_u32_array fail: %d\n", ret); return ret; } gpio_set_debounce(ints[0], ints[1]); pr_debug("in[0] = %d, in[1] = %d!!\n", ints[0], ints[1]); #endif CM36558_obj->irq = irq_of_parse_and_map(CM36558_obj->irq_node, 0); pr_debug("CM36558_obj->irq = %d\n", CM36558_obj->irq); if (!CM36558_obj->irq) { pr_err("irq_of_parse_and_map fail!!\n"); return -EINVAL; } if (request_irq(CM36558_obj->irq, CM36558_eint_handler, IRQF_TRIGGER_NONE, "ALS-eint", NULL)) { pr_err("IRQ LINE NOT AVAILABLE!!\n"); return -EINVAL; } enable_irq_wake(CM36558_obj->irq); /*enable_irq(CM36558_obj->irq);*/ } else { pr_err("null irq node!!\n"); return -EINVAL; } return 0; } static int set_psensor_threshold(struct i2c_client *client) { struct CM36558_priv *obj = i2c_get_clientdata(client); int res = 0; u8 databuf[3]; pr_info("%s function high: 0x%x, low:0x%x\n", __func__, atomic_read(&obj->ps_thd_val_high), atomic_read(&obj->ps_thd_val_low)); databuf[0] = CM36558_REG_PS_THDL; databuf[1] = (u8)(atomic_read(&obj->ps_thd_val_low) & 0xFF); databuf[2] = (u8)(atomic_read(&obj->ps_thd_val_low) >> 8); res = CM36558_i2c_master_operate(client, databuf, 0x3, I2C_FLAG_WRITE); if (res <= 0) { pr_err("i2c_master_send function err\n"); return -1; } databuf[0] = CM36558_REG_PS_THDH; databuf[1] = (u8)(atomic_read(&obj->ps_thd_val_high) & 0xFF); databuf[2] = (u8)(atomic_read(&obj->ps_thd_val_high) >> 8); res = CM36558_i2c_master_operate(client, databuf, 0x3, I2C_FLAG_WRITE); if (res <= 0) { pr_err("i2c_master_send function err\n"); return -1; } return 0; } static int CM36558_init_client(struct i2c_client *client) { struct CM36558_priv *obj = i2c_get_clientdata(client); u8 databuf[3]; int res = 0; pr_debug("%s\n", __func__); databuf[0] = CM36558_REG_ALS_UV_CONF; if (obj->hw.polling_mode_als == 1) databuf[1] = 0x01; else databuf[1] = 0x03; databuf[2] = 0x01; res = CM36558_i2c_master_operate(client, databuf, 0x3, I2C_FLAG_WRITE); if (res <= 0) { pr_err("i2c_master_send function err\n"); goto EXIT_ERR; } pr_debug("CM36558 ps CM36558_REG_ALS_UV_CONF command!\n"); databuf[0] = CM36558_REG_PS_CONF1_2; databuf[1] = 0x01; if (obj->hw.polling_mode_ps == 1) databuf[2] = 0x00; else databuf[2] = 0x03; res = CM36558_i2c_master_operate(client, databuf, 0x3, I2C_FLAG_WRITE); if (res <= 0) { pr_err("i2c_master_send function err\n"); goto EXIT_ERR; } pr_debug("CM36558 ps CM36558_REG_PS_CONF1_2 command!\n"); databuf[0] = CM36558_REG_PS_CONF3_MS; databuf[1] = 0x30 /*0b00110000*/; databuf[2] = 0x02 /*0b00000010*/; res = CM36558_i2c_master_operate(client, databuf, 0x3, I2C_FLAG_WRITE); if (res <= 0) { pr_err("i2c_master_send function err\n"); goto EXIT_ERR; } pr_debug("CM36558 ps CM36558_REG_PS_CONF3_MS command!\n"); databuf[0] = CM36558_REG_PS_CANC; databuf[1] = 0x00; databuf[2] = 0x00; res = CM36558_i2c_master_operate(client, databuf, 0x3, I2C_FLAG_WRITE); if (res <= 0) { pr_err("i2c_master_send function err\n"); goto EXIT_ERR; } pr_debug("CM36558 ps CM36558_REG_PS_CANC command!\n"); if (obj->hw.polling_mode_als == 0) { databuf[0] = CM36558_REG_ALS_THDH; databuf[1] = (u8)(atomic_read(&obj->als_thd_val_high) & 0xFF); databuf[2] = (u8)(atomic_read(&obj->als_thd_val_high) >> 8); res = CM36558_i2c_master_operate(client, databuf, 0x3, I2C_FLAG_WRITE); if (res <= 0) { pr_err("i2c_master_send function err\n"); goto EXIT_ERR; } databuf[0] = CM36558_REG_ALS_THDL; databuf[1] = (u8)(atomic_read(&obj->als_thd_val_low) & 0xFF); databuf[2] = (u8)(atomic_read(&obj->als_thd_val_low) >> 8); res = CM36558_i2c_master_operate(client, databuf, 0x3, I2C_FLAG_WRITE); if (res <= 0) { pr_err("i2c_master_send function err\n"); goto EXIT_ERR; } } if (obj->hw.polling_mode_ps == 0) { databuf[0] = CM36558_REG_PS_THDL; databuf[1] = (u8)(atomic_read(&obj->ps_thd_val_low) & 0xFF); databuf[2] = (u8)(atomic_read(&obj->ps_thd_val_low) >> 8); res = CM36558_i2c_master_operate(client, databuf, 0x3, I2C_FLAG_WRITE); if (res <= 0) { pr_err("i2c_master_send function err\n"); goto EXIT_ERR; } databuf[0] = CM36558_REG_PS_THDH; databuf[1] = (u8)(atomic_read(&obj->ps_thd_val_high) & 0xFF); databuf[2] = (u8)(atomic_read(&obj->ps_thd_val_high) >> 8); res = CM36558_i2c_master_operate(client, databuf, 0x3, I2C_FLAG_WRITE); if (res <= 0) { pr_err("i2c_master_send function err\n"); goto EXIT_ERR; } } res = CM36558_setup_eint(client); if (res != 0) { pr_err("setup eint: %d\n", res); return res; } return CM36558_SUCCESS; EXIT_ERR: pr_err("init dev: %d\n", res); return res; } static int als_open_report_data(int open) { return 0; } static int als_enable_nodata(int en) { int res = 0; pr_info("CM36558_obj als enable value = %d\n", en); mutex_lock(&CM36558_mutex); if (en) set_bit(CMC_BIT_ALS, &CM36558_obj->enable); else clear_bit(CMC_BIT_ALS, &CM36558_obj->enable); mutex_unlock(&CM36558_mutex); if (!CM36558_obj) { pr_err("CM36558_obj is null!!\n"); return -1; } res = CM36558_enable_als(CM36558_obj->client, en); if (res) { pr_err("%s is failed!!\n", __func__); return -1; } return 0; } static int als_set_delay(u64 ns) { return 0; } static int als_batch(int flag, int64_t samplingPeriodNs, int64_t maxBatchReportLatencyNs) { return als_set_delay(samplingPeriodNs); } static int als_flush(void) { return als_flush_report(); } static int als_get_data(int *value, int *status) { int err = 0; struct CM36558_priv *obj = NULL; if (!CM36558_obj) { pr_err("CM36558_obj is null!!\n"); return -1; } obj = CM36558_obj; err = CM36558_read_als(obj->client, &obj->als); if (err) { err = -1; } else { *value = CM36558_get_als_value(obj, obj->als); *status = SENSOR_STATUS_ACCURACY_MEDIUM; } return err; } static int ps_open_report_data(int open) { return 0; } static int ps_enable_nodata(int en) { int res = 0; pr_debug("CM36558_obj als enable value = %d\n", en); mutex_lock(&CM36558_mutex); if (en) set_bit(CMC_BIT_PS, &CM36558_obj->enable); else clear_bit(CMC_BIT_PS, &CM36558_obj->enable); mutex_unlock(&CM36558_mutex); if (!CM36558_obj) { pr_err("CM36558_obj is null!!\n"); return -1; } res = CM36558_enable_ps(CM36558_obj->client, en); if (res) { pr_err("als_enable_nodata is failed!!\n"); return -1; } /*Report default ps value(far away) when enable ps*/ if (en != 0) ps_data_report(1, 3); return 0; } static int ps_set_delay(u64 ns) { return 0; } static int ps_batch(int flag, int64_t samplingPeriodNs, int64_t maxBatchReportLatencyNs) { return 0; } static int ps_flush(void) { return ps_flush_report(); } static int ps_get_data(int *value, int *status) { int err = 0; if (!CM36558_obj) { pr_err("CM36558_obj is null!!\n"); return -1; } err = CM36558_read_ps(CM36558_obj->client, &CM36558_obj->ps); if (err) { err = -1; } else { *value = CM36558_get_ps_value(CM36558_obj, CM36558_obj->ps); *status = SENSOR_STATUS_ACCURACY_MEDIUM; } return 0; } static int cm36558_als_factory_enable_sensor(bool enable_disable, int64_t sample_periods_ms) { int err = 0; err = als_enable_nodata(enable_disable ? 1 : 0); if (err) { pr_err("%s:%s failed\n", __func__, enable_disable ? "enable" : "disable"); return -1; } err = als_batch(0, sample_periods_ms * 1000000, 0); if (err) { pr_err("%s set_batch failed\n", __func__); return -1; } return 0; } static int cm36558_als_factory_get_data(int32_t *data) { int status; return als_get_data(data, &status); } static int cm36558_als_factory_get_raw_data(int32_t *data) { int err = 0; struct CM36558_priv *obj = CM36558_obj; if (!obj) { pr_err("obj is null!!\n"); return -1; } err = CM36558_read_als(obj->client, &obj->als); if (err) { pr_err("%s failed\n", __func__); return -1; } *data = CM36558_obj->als; return 0; } static int cm36558_als_factory_enable_calibration(void) { return 0; } static int cm36558_als_factory_clear_cali(void) { return 0; } static int cm36558_als_factory_set_cali(int32_t offset) { return 0; } static int cm36558_als_factory_get_cali(int32_t *offset) { return 0; } static int cm36558_ps_factory_enable_sensor(bool enable_disable, int64_t sample_periods_ms) { int err = 0; err = ps_enable_nodata(enable_disable ? 1 : 0); if (err) { pr_err("%s:%s failed\n", __func__, enable_disable ? "enable" : "disable"); return -1; } err = ps_batch(0, sample_periods_ms * 1000000, 0); if (err) { pr_err("%s set_batch failed\n", __func__); return -1; } return err; } static int cm36558_ps_factory_get_data(int32_t *data) { int err = 0, status = 0; err = ps_get_data(data, &status); if (err < 0) return -1; return 0; } static int cm36558_ps_factory_get_raw_data(int32_t *data) { int err = 0; struct CM36558_priv *obj = CM36558_obj; err = CM36558_read_ps(obj->client, &obj->ps); if (err) { pr_err("%s failed\n", __func__); return -1; } *data = CM36558_obj->ps; return 0; } static int cm36558_ps_factory_enable_calibration(void) { return 0; } static int cm36558_ps_factory_clear_cali(void) { struct CM36558_priv *obj = CM36558_obj; obj->ps_cali = 0; return 0; } static int cm36558_ps_factory_set_cali(int32_t offset) { struct CM36558_priv *obj = CM36558_obj; obj->ps_cali = offset; return 0; } static int cm36558_ps_factory_get_cali(int32_t *offset) { struct CM36558_priv *obj = CM36558_obj; *offset = obj->ps_cali; return 0; } static int cm36558_ps_factory_set_threshold(int32_t threshold[2]) { int err = 0; struct CM36558_priv *obj = CM36558_obj; pr_info("%s set threshold high: 0x%x, low: 0x%x\n", __func__, threshold[0], threshold[1]); atomic_set(&obj->ps_thd_val_high, (threshold[0] + obj->ps_cali)); atomic_set(&obj->ps_thd_val_low, (threshold[1] + obj->ps_cali)); err = set_psensor_threshold(obj->client); if (err < 0) { pr_err("set_psensor_threshold fail\n"); return -1; } return 0; } static int cm36558_ps_factory_get_threshold(int32_t threshold[2]) { struct CM36558_priv *obj = CM36558_obj; threshold[0] = atomic_read(&obj->ps_thd_val_high) - obj->ps_cali; threshold[1] = atomic_read(&obj->ps_thd_val_low) - obj->ps_cali; return 0; } static struct alsps_factory_fops cm36558_factory_fops = { .als_enable_sensor = cm36558_als_factory_enable_sensor, .als_get_data = cm36558_als_factory_get_data, .als_get_raw_data = cm36558_als_factory_get_raw_data, .als_enable_calibration = cm36558_als_factory_enable_calibration, .als_clear_cali = cm36558_als_factory_clear_cali, .als_set_cali = cm36558_als_factory_set_cali, .als_get_cali = cm36558_als_factory_get_cali, .ps_enable_sensor = cm36558_ps_factory_enable_sensor, .ps_get_data = cm36558_ps_factory_get_data, .ps_get_raw_data = cm36558_ps_factory_get_raw_data, .ps_enable_calibration = cm36558_ps_factory_enable_calibration, .ps_clear_cali = cm36558_ps_factory_clear_cali, .ps_set_cali = cm36558_ps_factory_set_cali, .ps_get_cali = cm36558_ps_factory_get_cali, .ps_set_threshold = cm36558_ps_factory_set_threshold, .ps_get_threshold = cm36558_ps_factory_get_threshold, }; static struct alsps_factory_public cm36558_factory_device = { .gain = 1, .sensitivity = 1, .fops = &cm36558_factory_fops, }; static int CM36558_i2c_probe(struct i2c_client *client, const struct i2c_device_id *id) { struct CM36558_priv *obj = NULL; int err = 0; struct als_control_path als_ctl = {0}; struct als_data_path als_data = {0}; struct ps_control_path ps_ctl = {0}; struct ps_data_path ps_data = {0}; pr_debug("%s\n", __func__); /* get customization and power on */ obj = kzalloc(sizeof(*obj), GFP_KERNEL); if (!obj) { err = -ENOMEM; goto exit; } err = get_alsps_dts_func(client->dev.of_node, &obj->hw); if (err < 0) { pr_err("get customization info from dts failed\n"); goto exit_init_failed; } CM36558_obj = obj; INIT_WORK(&obj->eint_work, CM36558_eint_work); obj->client = client; i2c_set_clientdata(client, obj); atomic_set(&obj->als_debounce, 200); atomic_set(&obj->als_deb_on, 0); atomic_set(&obj->als_deb_end, 0); atomic_set(&obj->ps_debounce, 200); atomic_set(&obj->ps_deb_on, 0); atomic_set(&obj->ps_deb_end, 0); atomic_set(&obj->ps_mask, 0); atomic_set(&obj->als_suspend, 0); atomic_set(&obj->als_cmd_val, 0xDF); atomic_set(&obj->ps_cmd_val, 0xC1); atomic_set(&obj->ps_thd_val_high, obj->hw.ps_threshold_high); atomic_set(&obj->ps_thd_val_low, obj->hw.ps_threshold_low); atomic_set(&obj->als_thd_val_high, obj->hw.als_threshold_high); atomic_set(&obj->als_thd_val_low, obj->hw.als_threshold_low); atomic_set(&obj->init_done, 0); obj->irq_node = client->dev.of_node; obj->enable = 0; obj->pending_intr = 0; obj->ps_cali = 0; obj->als_level_num = ARRAY_SIZE(obj->hw.als_level); obj->als_value_num = ARRAY_SIZE(obj->hw.als_value); WARN_ON(sizeof(obj->als_level) != sizeof(obj->hw.als_level)); memcpy(obj->als_level, obj->hw.als_level, sizeof(obj->als_level)); WARN_ON(sizeof(obj->als_value) != sizeof(obj->hw.als_value)); memcpy(obj->als_value, obj->hw.als_value, sizeof(obj->als_value)); atomic_set(&obj->i2c_retry, 3); clear_bit(CMC_BIT_ALS, &obj->enable); clear_bit(CMC_BIT_PS, &obj->enable); CM36558_i2c_client = client; err = CM36558_init_client(client); if (err) goto exit_init_failed; pr_debug("CM36558_init_client() OK!\n"); /* err = misc_register(&CM36558_device); */ err = alsps_factory_device_register(&cm36558_factory_device); if (err) { pr_err("CM36558_device register failed\n"); goto exit_misc_device_register_failed; } als_ctl.is_use_common_factory = false; ps_ctl.is_use_common_factory = false; pr_debug("CM36558_device misc_register OK!\n"); err = CM36558_create_attr( &(CM36558_init_info.platform_diver_addr->driver)); if (err) { pr_err("create attribute err = %d\n", err); goto exit_create_attr_failed; } als_ctl.open_report_data = als_open_report_data; als_ctl.enable_nodata = als_enable_nodata; als_ctl.set_delay = als_set_delay; als_ctl.batch = als_batch; als_ctl.flush = als_flush; als_ctl.is_report_input_direct = false; als_ctl.is_support_batch = false; err = als_register_control_path(&als_ctl); if (err) { pr_err("register fail = %d\n", err); goto exit_sensor_obj_attach_fail; } als_data.get_data = als_get_data; als_data.vender_div = 100; err = als_register_data_path(&als_data); if (err) { pr_err("tregister fail = %d\n", err); goto exit_sensor_obj_attach_fail; } ps_ctl.open_report_data = ps_open_report_data; ps_ctl.enable_nodata = ps_enable_nodata; ps_ctl.set_delay = ps_set_delay; ps_ctl.batch = ps_batch; ps_ctl.flush = ps_flush; ps_ctl.is_report_input_direct = true; ps_ctl.is_support_batch = false; err = ps_register_control_path(&ps_ctl); if (err) { pr_err("register fail = %d\n", err); goto exit_sensor_obj_attach_fail; } ps_data.get_data = ps_get_data; ps_data.vender_div = 100; err = ps_register_data_path(&ps_data); if (err) { pr_err("tregister fail = %d\n", err); goto exit_sensor_obj_attach_fail; } CM36558_init_flag = 0; pr_debug("%s: OK\n", __func__); return 0; exit_create_attr_failed: exit_sensor_obj_attach_fail: exit_misc_device_register_failed: /* misc_deregister(&CM36558_device); */ exit_init_failed: kfree(obj); exit: obj = NULL; CM36558_obj = NULL; CM36558_i2c_client = NULL; pr_err("%s: err = %d\n", __func__, err); CM36558_init_flag = -1; return err; } static int CM36558_i2c_remove(struct i2c_client *client) { int err = 0; err = CM36558_delete_attr( &(CM36558_init_info.platform_diver_addr->driver)); if (err) pr_err("CM36558_delete_attr fail: %d\n", err); alsps_factory_device_deregister(&cm36558_factory_device); CM36558_i2c_client = NULL; i2c_unregister_device(client); kfree(i2c_get_clientdata(client)); return 0; } static int CM36558_i2c_detect(struct i2c_client *client, struct i2c_board_info *info) { strlcpy(info->type, CM36558_DEV_NAME, sizeof(info->type)); return 0; } static int CM36558_i2c_suspend(struct device *dev) { struct i2c_client *client = to_i2c_client(dev); struct CM36558_priv *obj = i2c_get_clientdata(client); int err = 0; pr_debug("%s\n", __func__); if (!obj) { pr_err("null pointer!!\n"); return 0; } atomic_set(&obj->als_suspend, 1); err = CM36558_enable_als(obj->client, 0); if (err) pr_err("disable als fail: %d\n", err); return 0; } static int CM36558_i2c_resume(struct device *dev) { struct i2c_client *client = to_i2c_client(dev); struct CM36558_priv *obj = i2c_get_clientdata(client); int err = 0; pr_debug("%s\n", __func__); if (!obj) { pr_err("null pointer!!\n"); return 0; } atomic_set(&obj->als_suspend, 0); if (test_bit(CMC_BIT_ALS, &obj->enable)) { err = CM36558_enable_als(obj->client, 1); if (err) pr_err("enable als fail: %d\n", err); } return 0; } /*----------------------------------------------------------------------------*/ /*----------------------------------------------------------------------------*/ static int CM36558_remove(void) { i2c_del_driver(&CM36558_i2c_driver); return 0; } /*----------------------------------------------------------------------------*/ static int CM36558_local_init(void) { if (i2c_add_driver(&CM36558_i2c_driver)) { pr_err("add driver error\n"); return -1; } if (-1 == CM36558_init_flag) return -1; return 0; } /*----------------------------------------------------------------------------*/ static int __init CM36558_init(void) { alsps_driver_add(&CM36558_init_info); return 0; } /*----------------------------------------------------------------------------*/ static void __exit CM36558_exit(void) { pr_debug("%s\n", __func__); } /*----------------------------------------------------------------------------*/ module_init(CM36558_init); module_exit(CM36558_exit); /*----------------------------------------------------------------------------*/ MODULE_AUTHOR("yucong xiong"); MODULE_DESCRIPTION("CM36558 driver"); MODULE_LICENSE("GPL");