| /** |
| * Temperature sensor driver |
| * Copyright (C) 2008 - 2014 Quantenna Communications Inc |
| * |
| * This program is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU General Public License |
| * as published by the Free Software Foundation; either version 2 |
| * of the License, or (at your option) any later version. |
| * |
| * This 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
| **/ |
| |
| #include <linux/jiffies.h> |
| #include <linux/mutex.h> |
| #include <linux/platform_device.h> |
| #include <linux/i2c.h> |
| #include <linux/slab.h> |
| #include <linux/kernel.h> |
| #include <linux/module.h> |
| #include <linux/version.h> |
| |
| |
| #define SE95_REG_TEMP 0x00 |
| #define SE95_REG_CONF 0x01 |
| #define SE95_REG_THYST 0x02 |
| #define SE95_REG_TOS 0x03 |
| #define SE95_REG_ID 0x05 |
| |
| #define SE95_MANUFACTURER_ID 0xA1 |
| /* |
| * qcsapi guarantees temperature value to be no more than 5 seconds old. |
| * Do not break that promise. |
| */ |
| #define SE95_TEMP_UPDATE_PERIOD (4 * HZ) |
| |
| #define SE95_DATA_VAL_RESOL 3125 |
| #define TWOS_COMP_13BIT(X) ((X > 4096) ? (X - 8192) : X) |
| |
| /* |
| * Temperature sensor messages involve sending the address as a write, |
| * then performing the desired read operation |
| */ |
| enum { |
| SE95_MSG_IDX_WRITE = 0, |
| SE95_MSG_IDX_READ = 1, |
| SE95_MSG_LEN = 2, |
| }; |
| |
| typedef struct { |
| int temp_cal_13; /* Current temperature with 13-bit precision */ |
| unsigned long next_update_ts; |
| struct mutex update_mutex; |
| } qtn_tsensor_state; |
| |
| static int qtn_tsensor_send_message(struct i2c_client *client, |
| uint8_t addr, void* buf, size_t len) |
| { |
| int ret; |
| struct i2c_msg msgs[SE95_MSG_LEN]; |
| |
| if (unlikely(!client || !client->adapter)) { |
| return -ENODEV; |
| } |
| |
| msgs[SE95_MSG_IDX_WRITE].addr = client->addr; |
| msgs[SE95_MSG_IDX_WRITE].flags = 0; |
| msgs[SE95_MSG_IDX_WRITE].buf = &addr; |
| msgs[SE95_MSG_IDX_WRITE].len = sizeof(addr); |
| msgs[SE95_MSG_IDX_READ].addr = client->addr; |
| msgs[SE95_MSG_IDX_READ].flags = I2C_M_RD; |
| msgs[SE95_MSG_IDX_READ].buf = buf; |
| msgs[SE95_MSG_IDX_READ].len = len; |
| |
| ret = i2c_transfer(client->adapter, msgs, SE95_MSG_LEN); |
| if (ret == SE95_MSG_LEN) { |
| ret = 0; |
| } else if (ret >= 0) { |
| ret = -EIO; |
| } |
| |
| return ret; |
| } |
| |
| /* |
| * Convert raw temperature data from sensor to an actual temperature value (datasheet formula): |
| * 1. If the temp_data MSB = 0, then: Temp (°C) = +(temp_data) × value resolution |
| * 2. If the temp_data MSB = 1, then: Temp (°C) = −(two’s complement temp_data) × value resolution |
| */ |
| static inline int qtn_tsensor_convert_sdata_to_temp(uint16_t temp_data) |
| { |
| return TWOS_COMP_13BIT(temp_data) * SE95_DATA_VAL_RESOL; |
| } |
| |
| static inline int qtn_tsensor_update_temperature(struct i2c_client *client) |
| { |
| qtn_tsensor_state *se95_state = i2c_get_clientdata(client); |
| uint8_t temp[2] = {0, 0}; |
| int ret = 0; |
| |
| mutex_lock(&se95_state->update_mutex); |
| |
| if (!time_after(jiffies, se95_state->next_update_ts)) { |
| goto exit; |
| } |
| |
| ret = qtn_tsensor_send_message(client, SE95_REG_TEMP, temp, sizeof(temp)); |
| if (ret < 0) { |
| dev_warn(&client->dev, "Failed to read temperature from sensor\n"); |
| se95_state->temp_cal_13 = 0; |
| } else { |
| uint16_t temp_cal_13 = (temp[0] << 8 | (temp[1] & 0xF8)) >> 3; |
| se95_state->temp_cal_13 = qtn_tsensor_convert_sdata_to_temp(temp_cal_13); |
| } |
| |
| se95_state->next_update_ts = jiffies + SE95_TEMP_UPDATE_PERIOD; |
| |
| exit: |
| mutex_unlock(&se95_state->update_mutex); |
| return ret; |
| } |
| |
| static int qtn_tsensor_probe(struct i2c_client *client, |
| const struct i2c_device_id *dev_id) |
| { |
| int ret; |
| uint8_t id; |
| uint8_t over_thres[2] = {0, 0}; |
| uint8_t hyster[2] = {0, 0}; |
| qtn_tsensor_state *se95_state = NULL; |
| |
| ret = qtn_tsensor_send_message(client, SE95_REG_ID, &id, sizeof(id)); |
| if (ret < 0) { |
| dev_dbg(&client->dev, "Failed reading sensor ID: %d\n", ret); |
| return ret; |
| } |
| if (id != SE95_MANUFACTURER_ID) { |
| dev_dbg(&client->dev, "Unknown temperature sensor ID: 0x%x\n", id); |
| return -ENODEV; |
| } |
| |
| ret = qtn_tsensor_send_message(client, SE95_REG_TOS, over_thres, |
| sizeof(over_thres)); |
| if (ret < 0) { |
| dev_dbg(&client->dev, "Failed reading overtemp threshold: %d\n", ret); |
| return ret; |
| } |
| |
| ret = qtn_tsensor_send_message(client, SE95_REG_THYST, hyster, |
| sizeof(hyster)); |
| if (ret < 0) { |
| dev_dbg(&client->dev, "Failed reading hysteresis: %d\n", ret); |
| return ret; |
| } |
| |
| se95_state = kzalloc(sizeof(qtn_tsensor_state), GFP_KERNEL); |
| if (!se95_state) { |
| return -ENOMEM; |
| } |
| |
| i2c_set_clientdata(client, se95_state); |
| se95_state->next_update_ts = jiffies; |
| mutex_init(&se95_state->update_mutex); |
| |
| printk(KERN_DEBUG "temp_sensor: id=0x%x\n\tover temp thresh=%x %x\n\t" |
| "hysteresis=%x %x\n", |
| id, over_thres[0], over_thres[1], hyster[0], hyster[1]); |
| |
| return 0; |
| } |
| |
| #if LINUX_VERSION_CODE >= KERNEL_VERSION(4,7,0) |
| static int qtn_tsensor_remove(struct i2c_client *client) |
| #else |
| static int __devexit qtn_tsensor_remove(struct i2c_client *client) |
| #endif |
| { |
| qtn_tsensor_state *se95_state = i2c_get_clientdata(client); |
| |
| if (se95_state) { |
| i2c_set_clientdata(client, NULL); |
| kfree(se95_state); |
| } |
| return 0; |
| } |
| |
| static const struct i2c_device_id se95_ids[] = { |
| { "se95", 0x49 }, |
| { } |
| }; |
| |
| static struct i2c_driver se95_driver = { |
| .driver = { |
| .name = "se95", |
| .owner = THIS_MODULE, |
| }, |
| .probe = qtn_tsensor_probe, |
| |
| #if LINUX_VERSION_CODE >= KERNEL_VERSION(4,7,0) |
| .remove = qtn_tsensor_remove, |
| #else |
| .remove = __devexit_p(qtn_tsensor_remove), |
| #endif |
| .class = I2C_CLASS_HWMON, |
| .id_table = se95_ids, |
| }; |
| |
| static int __init qtn_tsensor_init(void) |
| { |
| return i2c_add_driver(&se95_driver); |
| } |
| |
| static void __exit qtn_tsensor_exit(void) |
| { |
| i2c_del_driver(&se95_driver); |
| } |
| |
| module_init (qtn_tsensor_init); |
| module_exit (qtn_tsensor_exit); |
| |
| MODULE_AUTHOR("Quantenna Communications"); |
| MODULE_DESCRIPTION("Temperature sensor I2C driver"); |
| MODULE_LICENSE("GPL"); |
| |
| int qtn_tsensor_get_temperature(struct i2c_client *client, int *val) |
| { |
| qtn_tsensor_state *se95_state; |
| int ret; |
| |
| if (!client) { |
| return -ENODEV; |
| } |
| |
| se95_state = i2c_get_clientdata(client); |
| if (!se95_state) { |
| return -ENODEV; |
| } |
| |
| ret = qtn_tsensor_update_temperature(client); |
| *val = se95_state->temp_cal_13; |
| |
| return ret; |
| } |
| EXPORT_SYMBOL(qtn_tsensor_get_temperature); |