blob: 1016f91b30795cc2ef44acf8c4fc69c15f1b0378 [file] [log] [blame]
/**
* 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);