blob: 6ccceae21f701aacd7bd64c671925d7f79bde9bd [file] [log] [blame] [edit]
// SPDX-License-Identifier: GPL-2.0+
/*
* cc2.c - Support for the Amphenol ChipCap 2 relative humidity, temperature sensor
*
* Part numbers supported:
* CC2D23, CC2D23S, CC2D25, CC2D25S, CC2D33, CC2D33S, CC2D35, CC2D35S
*
* Author: Javier Carrasco <javier.carrasco.cruz@gmail.com>
*
* Datasheet and application notes:
* https://www.amphenol-sensors.com/en/telaire/humidity/527-humidity-sensors/3095-chipcap-2
*/
#include <linux/bitfield.h>
#include <linux/bits.h>
#include <linux/completion.h>
#include <linux/delay.h>
#include <linux/hwmon.h>
#include <linux/i2c.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/module.h>
#include <linux/regulator/consumer.h>
#define CC2_START_CM 0xA0
#define CC2_START_NOM 0x80
#define CC2_R_ALARM_H_ON 0x18
#define CC2_R_ALARM_H_OFF 0x19
#define CC2_R_ALARM_L_ON 0x1A
#define CC2_R_ALARM_L_OFF 0x1B
#define CC2_RW_OFFSET 0x40
#define CC2_W_ALARM_H_ON (CC2_R_ALARM_H_ON + CC2_RW_OFFSET)
#define CC2_W_ALARM_H_OFF (CC2_R_ALARM_H_OFF + CC2_RW_OFFSET)
#define CC2_W_ALARM_L_ON (CC2_R_ALARM_L_ON + CC2_RW_OFFSET)
#define CC2_W_ALARM_L_OFF (CC2_R_ALARM_L_OFF + CC2_RW_OFFSET)
#define CC2_STATUS_FIELD GENMASK(7, 6)
#define CC2_STATUS_VALID_DATA 0x00
#define CC2_STATUS_STALE_DATA 0x01
#define CC2_STATUS_CMD_MODE 0x02
#define CC2_RESPONSE_FIELD GENMASK(1, 0)
#define CC2_RESPONSE_BUSY 0x00
#define CC2_RESPONSE_ACK 0x01
#define CC2_RESPONSE_NACK 0x02
#define CC2_ERR_CORR_EEPROM BIT(2)
#define CC2_ERR_UNCORR_EEPROM BIT(3)
#define CC2_ERR_RAM_PARITY BIT(4)
#define CC2_ERR_CONFIG_LOAD BIT(5)
#define CC2_EEPROM_SIZE 10
#define CC2_EEPROM_DATA_LEN 3
#define CC2_MEASUREMENT_DATA_LEN 4
#define CC2_RH_DATA_FIELD GENMASK(13, 0)
/* ensure clean off -> on transitions */
#define CC2_POWER_CYCLE_MS 80
#define CC2_STARTUP_TO_DATA_MS 55
#define CC2_RESP_START_CM_US 100
#define CC2_RESP_EEPROM_R_US 100
#define CC2_RESP_EEPROM_W_MS 12
#define CC2_STARTUP_TIME_US 1250
#define CC2_RH_MAX (100 * 1000U)
#define CC2_CM_RETRIES 5
struct cc2_rh_alarm_info {
bool low_alarm;
bool high_alarm;
bool low_alarm_visible;
bool high_alarm_visible;
};
struct cc2_data {
struct cc2_rh_alarm_info rh_alarm;
struct completion complete;
struct device *hwmon;
struct i2c_client *client;
struct mutex dev_access_lock; /* device access lock */
struct regulator *regulator;
const char *name;
int irq_ready;
int irq_low;
int irq_high;
bool process_irqs;
};
enum cc2_chan_addr {
CC2_CHAN_TEMP = 0,
CC2_CHAN_HUMIDITY,
};
/* %RH as a per cent mille from a register value */
static long cc2_rh_convert(u16 data)
{
unsigned long tmp = (data & CC2_RH_DATA_FIELD) * CC2_RH_MAX;
return tmp / ((1 << 14) - 1);
}
/* convert %RH to a register value */
static u16 cc2_rh_to_reg(long data)
{
return data * ((1 << 14) - 1) / CC2_RH_MAX;
}
/* temperature in milli degrees celsius from a register value */
static long cc2_temp_convert(u16 data)
{
unsigned long tmp = ((data >> 2) * 165 * 1000U) / ((1 << 14) - 1);
return tmp - 40 * 1000U;
}
static int cc2_enable(struct cc2_data *data)
{
int ret;
/* exclusive regulator, check in case a disable failed */
if (regulator_is_enabled(data->regulator))
return 0;
/* clear any pending completion */
try_wait_for_completion(&data->complete);
ret = regulator_enable(data->regulator);
if (ret < 0)
return ret;
usleep_range(CC2_STARTUP_TIME_US, CC2_STARTUP_TIME_US + 125);
data->process_irqs = true;
return 0;
}
static void cc2_disable(struct cc2_data *data)
{
int err;
/* ignore alarms triggered by voltage toggling when powering up */
data->process_irqs = false;
/* exclusive regulator, check in case an enable failed */
if (regulator_is_enabled(data->regulator)) {
err = regulator_disable(data->regulator);
if (err)
dev_dbg(&data->client->dev, "Failed to disable device");
}
}
static int cc2_cmd_response_diagnostic(struct device *dev, u8 status)
{
int resp;
if (FIELD_GET(CC2_STATUS_FIELD, status) != CC2_STATUS_CMD_MODE) {
dev_dbg(dev, "Command sent out of command window\n");
return -ETIMEDOUT;
}
resp = FIELD_GET(CC2_RESPONSE_FIELD, status);
switch (resp) {
case CC2_RESPONSE_ACK:
return 0;
case CC2_RESPONSE_BUSY:
return -EBUSY;
case CC2_RESPONSE_NACK:
if (resp & CC2_ERR_CORR_EEPROM)
dev_dbg(dev, "Command failed: corrected EEPROM\n");
if (resp & CC2_ERR_UNCORR_EEPROM)
dev_dbg(dev, "Command failed: uncorrected EEPROM\n");
if (resp & CC2_ERR_RAM_PARITY)
dev_dbg(dev, "Command failed: RAM parity\n");
if (resp & CC2_ERR_RAM_PARITY)
dev_dbg(dev, "Command failed: configuration error\n");
return -ENODATA;
default:
dev_dbg(dev, "Unknown command reply\n");
return -EINVAL;
}
}
static int cc2_read_command_status(struct i2c_client *client)
{
u8 status;
int ret;
ret = i2c_master_recv(client, &status, 1);
if (ret != 1) {
ret = ret < 0 ? ret : -EIO;
return ret;
}
return cc2_cmd_response_diagnostic(&client->dev, status);
}
/*
* The command mode is only accessible after sending the START_CM command in the
* first 10 ms after power-up. Only in case the command window is missed,
* CC2_CM_RETRIES retries are attempted before giving up and returning an error.
*/
static int cc2_command_mode_start(struct cc2_data *data)
{
unsigned long timeout;
int i, ret;
for (i = 0; i < CC2_CM_RETRIES; i++) {
ret = cc2_enable(data);
if (ret < 0)
return ret;
ret = i2c_smbus_write_word_data(data->client, CC2_START_CM, 0);
if (ret < 0)
return ret;
if (data->irq_ready > 0) {
timeout = usecs_to_jiffies(2 * CC2_RESP_START_CM_US);
ret = wait_for_completion_timeout(&data->complete,
timeout);
if (!ret)
return -ETIMEDOUT;
} else {
usleep_range(CC2_RESP_START_CM_US,
2 * CC2_RESP_START_CM_US);
}
ret = cc2_read_command_status(data->client);
if (ret != -ETIMEDOUT || i == CC2_CM_RETRIES)
break;
/* command window missed, prepare for a retry */
cc2_disable(data);
msleep(CC2_POWER_CYCLE_MS);
}
return ret;
}
/* Sending a Start_NOM command finishes the command mode immediately with no
* reply and the device enters normal operation mode
*/
static int cc2_command_mode_finish(struct cc2_data *data)
{
int ret;
ret = i2c_smbus_write_word_data(data->client, CC2_START_NOM, 0);
if (ret < 0)
return ret;
return 0;
}
static int cc2_write_reg(struct cc2_data *data, u8 reg, u16 val)
{
unsigned long timeout;
int ret;
ret = cc2_command_mode_start(data);
if (ret < 0)
goto disable;
cpu_to_be16s(&val);
ret = i2c_smbus_write_word_data(data->client, reg, val);
if (ret < 0)
goto disable;
if (data->irq_ready > 0) {
timeout = msecs_to_jiffies(2 * CC2_RESP_EEPROM_W_MS);
ret = wait_for_completion_timeout(&data->complete, timeout);
if (!ret) {
ret = -ETIMEDOUT;
goto disable;
}
} else {
msleep(CC2_RESP_EEPROM_W_MS);
}
ret = cc2_read_command_status(data->client);
disable:
cc2_disable(data);
return ret;
}
static int cc2_read_reg(struct cc2_data *data, u8 reg, u16 *val)
{
u8 buf[CC2_EEPROM_DATA_LEN];
unsigned long timeout;
int ret;
ret = cc2_command_mode_start(data);
if (ret < 0)
return ret;
ret = i2c_smbus_write_word_data(data->client, reg, 0);
if (ret < 0)
return ret;
if (data->irq_ready > 0) {
timeout = usecs_to_jiffies(2 * CC2_RESP_EEPROM_R_US);
ret = wait_for_completion_timeout(&data->complete, timeout);
if (!ret)
return -ETIMEDOUT;
} else {
usleep_range(CC2_RESP_EEPROM_R_US, CC2_RESP_EEPROM_R_US + 10);
}
ret = i2c_master_recv(data->client, buf, CC2_EEPROM_DATA_LEN);
if (ret != CC2_EEPROM_DATA_LEN)
return ret < 0 ? ret : -EIO;
*val = be16_to_cpup((__be16 *)&buf[1]);
return cc2_read_command_status(data->client);
}
static int cc2_get_reg_val(struct cc2_data *data, u8 reg, long *val)
{
u16 reg_val;
int ret;
ret = cc2_read_reg(data, reg, &reg_val);
if (!ret)
*val = cc2_rh_convert(reg_val);
cc2_disable(data);
return ret;
}
static int cc2_data_fetch(struct i2c_client *client,
enum hwmon_sensor_types type, long *val)
{
u8 data[CC2_MEASUREMENT_DATA_LEN];
u8 status;
int ret;
ret = i2c_master_recv(client, data, CC2_MEASUREMENT_DATA_LEN);
if (ret != CC2_MEASUREMENT_DATA_LEN) {
ret = ret < 0 ? ret : -EIO;
return ret;
}
status = FIELD_GET(CC2_STATUS_FIELD, data[0]);
if (status == CC2_STATUS_STALE_DATA)
return -EBUSY;
if (status != CC2_STATUS_VALID_DATA)
return -EIO;
switch (type) {
case hwmon_humidity:
*val = cc2_rh_convert(be16_to_cpup((__be16 *)&data[0]));
break;
case hwmon_temp:
*val = cc2_temp_convert(be16_to_cpup((__be16 *)&data[2]));
break;
default:
return -EINVAL;
}
return 0;
}
static int cc2_read_measurement(struct cc2_data *data,
enum hwmon_sensor_types type, long *val)
{
unsigned long timeout;
int ret;
if (data->irq_ready > 0) {
timeout = msecs_to_jiffies(CC2_STARTUP_TO_DATA_MS * 2);
ret = wait_for_completion_timeout(&data->complete, timeout);
if (!ret)
return -ETIMEDOUT;
} else {
msleep(CC2_STARTUP_TO_DATA_MS);
}
ret = cc2_data_fetch(data->client, type, val);
return ret;
}
/*
* A measurement requires enabling the device, waiting for the automatic
* measurement to finish, reading the measurement data and disabling the device
* again.
*/
static int cc2_measurement(struct cc2_data *data, enum hwmon_sensor_types type,
long *val)
{
int ret;
ret = cc2_enable(data);
if (ret)
return ret;
ret = cc2_read_measurement(data, type, val);
cc2_disable(data);
return ret;
}
/*
* In order to check alarm status, the corresponding ALARM_OFF (hysteresis)
* register must be read and a new measurement must be carried out to trigger
* the alarm signals. Given that the device carries out a measurement after
* exiting the command mode, there is no need to force two power-up sequences.
* Instead, a NOM command is sent and the device is disabled after the
* measurement is read.
*/
static int cc2_read_hyst_and_measure(struct cc2_data *data, u8 reg,
long *hyst, long *measurement)
{
u16 reg_val;
int ret;
ret = cc2_read_reg(data, reg, &reg_val);
if (ret)
goto disable;
*hyst = cc2_rh_convert(reg_val);
ret = cc2_command_mode_finish(data);
if (ret)
goto disable;
ret = cc2_read_measurement(data, hwmon_humidity, measurement);
disable:
cc2_disable(data);
return ret;
}
static umode_t cc2_is_visible(const void *data, enum hwmon_sensor_types type,
u32 attr, int channel)
{
const struct cc2_data *cc2 = data;
switch (type) {
case hwmon_humidity:
switch (attr) {
case hwmon_humidity_input:
return 0444;
case hwmon_humidity_min_alarm:
return cc2->rh_alarm.low_alarm_visible ? 0444 : 0;
case hwmon_humidity_max_alarm:
return cc2->rh_alarm.high_alarm_visible ? 0444 : 0;
case hwmon_humidity_min:
case hwmon_humidity_min_hyst:
return cc2->rh_alarm.low_alarm_visible ? 0644 : 0;
case hwmon_humidity_max:
case hwmon_humidity_max_hyst:
return cc2->rh_alarm.high_alarm_visible ? 0644 : 0;
default:
return 0;
}
case hwmon_temp:
switch (attr) {
case hwmon_temp_input:
return 0444;
default:
return 0;
}
default:
break;
}
return 0;
}
static irqreturn_t cc2_ready_interrupt(int irq, void *data)
{
struct cc2_data *cc2 = data;
if (cc2->process_irqs)
complete(&cc2->complete);
return IRQ_HANDLED;
}
static irqreturn_t cc2_low_interrupt(int irq, void *data)
{
struct cc2_data *cc2 = data;
if (cc2->process_irqs) {
hwmon_notify_event(cc2->hwmon, hwmon_humidity,
hwmon_humidity_min_alarm, CC2_CHAN_HUMIDITY);
cc2->rh_alarm.low_alarm = true;
}
return IRQ_HANDLED;
}
static irqreturn_t cc2_high_interrupt(int irq, void *data)
{
struct cc2_data *cc2 = data;
if (cc2->process_irqs) {
hwmon_notify_event(cc2->hwmon, hwmon_humidity,
hwmon_humidity_max_alarm, CC2_CHAN_HUMIDITY);
cc2->rh_alarm.high_alarm = true;
}
return IRQ_HANDLED;
}
static int cc2_humidity_min_alarm_status(struct cc2_data *data, long *val)
{
long measurement, min_hyst;
int ret;
ret = cc2_read_hyst_and_measure(data, CC2_R_ALARM_L_OFF, &min_hyst,
&measurement);
if (ret < 0)
return ret;
if (data->rh_alarm.low_alarm) {
*val = (measurement < min_hyst) ? 1 : 0;
data->rh_alarm.low_alarm = *val;
} else {
*val = 0;
}
return 0;
}
static int cc2_humidity_max_alarm_status(struct cc2_data *data, long *val)
{
long measurement, max_hyst;
int ret;
ret = cc2_read_hyst_and_measure(data, CC2_R_ALARM_H_OFF, &max_hyst,
&measurement);
if (ret < 0)
return ret;
if (data->rh_alarm.high_alarm) {
*val = (measurement > max_hyst) ? 1 : 0;
data->rh_alarm.high_alarm = *val;
} else {
*val = 0;
}
return 0;
}
static int cc2_read(struct device *dev, enum hwmon_sensor_types type, u32 attr,
int channel, long *val)
{
struct cc2_data *data = dev_get_drvdata(dev);
int ret = 0;
mutex_lock(&data->dev_access_lock);
switch (type) {
case hwmon_temp:
ret = cc2_measurement(data, type, val);
break;
case hwmon_humidity:
switch (attr) {
case hwmon_humidity_input:
ret = cc2_measurement(data, type, val);
break;
case hwmon_humidity_min:
ret = cc2_get_reg_val(data, CC2_R_ALARM_L_ON, val);
break;
case hwmon_humidity_min_hyst:
ret = cc2_get_reg_val(data, CC2_R_ALARM_L_OFF, val);
break;
case hwmon_humidity_max:
ret = cc2_get_reg_val(data, CC2_R_ALARM_H_ON, val);
break;
case hwmon_humidity_max_hyst:
ret = cc2_get_reg_val(data, CC2_R_ALARM_H_OFF, val);
break;
case hwmon_humidity_min_alarm:
ret = cc2_humidity_min_alarm_status(data, val);
break;
case hwmon_humidity_max_alarm:
ret = cc2_humidity_max_alarm_status(data, val);
break;
default:
ret = -EOPNOTSUPP;
}
break;
default:
ret = -EOPNOTSUPP;
}
mutex_unlock(&data->dev_access_lock);
return ret;
}
static int cc2_write(struct device *dev, enum hwmon_sensor_types type, u32 attr,
int channel, long val)
{
struct cc2_data *data = dev_get_drvdata(dev);
int ret;
u16 arg;
u8 cmd;
if (type != hwmon_humidity)
return -EOPNOTSUPP;
if (val < 0 || val > CC2_RH_MAX)
return -EINVAL;
mutex_lock(&data->dev_access_lock);
switch (attr) {
case hwmon_humidity_min:
cmd = CC2_W_ALARM_L_ON;
arg = cc2_rh_to_reg(val);
ret = cc2_write_reg(data, cmd, arg);
break;
case hwmon_humidity_min_hyst:
cmd = CC2_W_ALARM_L_OFF;
arg = cc2_rh_to_reg(val);
ret = cc2_write_reg(data, cmd, arg);
break;
case hwmon_humidity_max:
cmd = CC2_W_ALARM_H_ON;
arg = cc2_rh_to_reg(val);
ret = cc2_write_reg(data, cmd, arg);
break;
case hwmon_humidity_max_hyst:
cmd = CC2_W_ALARM_H_OFF;
arg = cc2_rh_to_reg(val);
ret = cc2_write_reg(data, cmd, arg);
break;
default:
ret = -EOPNOTSUPP;
break;
}
mutex_unlock(&data->dev_access_lock);
return ret;
}
static int cc2_request_ready_irq(struct cc2_data *data, struct device *dev)
{
int ret = 0;
data->irq_ready = fwnode_irq_get_byname(dev_fwnode(dev), "ready");
if (data->irq_ready > 0) {
init_completion(&data->complete);
ret = devm_request_threaded_irq(dev, data->irq_ready, NULL,
cc2_ready_interrupt,
IRQF_ONESHOT |
IRQF_TRIGGER_RISING,
dev_name(dev), data);
}
return ret;
}
static int cc2_request_alarm_irqs(struct cc2_data *data, struct device *dev)
{
int ret = 0;
data->irq_low = fwnode_irq_get_byname(dev_fwnode(dev), "low");
if (data->irq_low > 0) {
ret = devm_request_threaded_irq(dev, data->irq_low, NULL,
cc2_low_interrupt,
IRQF_ONESHOT |
IRQF_TRIGGER_RISING,
dev_name(dev), data);
if (ret)
return ret;
data->rh_alarm.low_alarm_visible = true;
}
data->irq_high = fwnode_irq_get_byname(dev_fwnode(dev), "high");
if (data->irq_high > 0) {
ret = devm_request_threaded_irq(dev, data->irq_high, NULL,
cc2_high_interrupt,
IRQF_ONESHOT |
IRQF_TRIGGER_RISING,
dev_name(dev), data);
if (ret)
return ret;
data->rh_alarm.high_alarm_visible = true;
}
return ret;
}
static const struct hwmon_channel_info *cc2_info[] = {
HWMON_CHANNEL_INFO(temp, HWMON_T_INPUT),
HWMON_CHANNEL_INFO(humidity, HWMON_H_INPUT | HWMON_H_MIN | HWMON_H_MAX |
HWMON_H_MIN_HYST | HWMON_H_MAX_HYST |
HWMON_H_MIN_ALARM | HWMON_H_MAX_ALARM),
NULL
};
static const struct hwmon_ops cc2_hwmon_ops = {
.is_visible = cc2_is_visible,
.read = cc2_read,
.write = cc2_write,
};
static const struct hwmon_chip_info cc2_chip_info = {
.ops = &cc2_hwmon_ops,
.info = cc2_info,
};
static int cc2_probe(struct i2c_client *client)
{
struct cc2_data *data;
struct device *dev = &client->dev;
int ret;
if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C))
return -EOPNOTSUPP;
data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
if (!data)
return -ENOMEM;
i2c_set_clientdata(client, data);
mutex_init(&data->dev_access_lock);
data->client = client;
data->regulator = devm_regulator_get_exclusive(dev, "vdd");
if (IS_ERR(data->regulator)) {
dev_err_probe(dev, PTR_ERR(data->regulator),
"Failed to get regulator\n");
return PTR_ERR(data->regulator);
}
ret = cc2_request_ready_irq(data, dev);
if (ret) {
dev_err_probe(dev, ret, "Failed to request ready irq\n");
return ret;
}
ret = cc2_request_alarm_irqs(data, dev);
if (ret) {
dev_err_probe(dev, ret, "Failed to request alarm irqs\n");
goto disable;
}
data->hwmon = devm_hwmon_device_register_with_info(dev, client->name,
data, &cc2_chip_info,
NULL);
if (IS_ERR(data->hwmon)) {
dev_err_probe(dev, PTR_ERR(data->hwmon),
"Failed to register hwmon device\n");
ret = PTR_ERR(data->hwmon);
}
disable:
cc2_disable(data);
return ret;
}
static void cc2_remove(struct i2c_client *client)
{
struct cc2_data *data = i2c_get_clientdata(client);
cc2_disable(data);
}
static const struct i2c_device_id cc2_id[] = {
{ "cc2d23" },
{ "cc2d23s" },
{ "cc2d25" },
{ "cc2d25s" },
{ "cc2d33" },
{ "cc2d33s" },
{ "cc2d35" },
{ "cc2d35s" },
{ }
};
MODULE_DEVICE_TABLE(i2c, cc2_id);
static const struct of_device_id cc2_of_match[] = {
{ .compatible = "amphenol,cc2d23" },
{ .compatible = "amphenol,cc2d23s" },
{ .compatible = "amphenol,cc2d25" },
{ .compatible = "amphenol,cc2d25s" },
{ .compatible = "amphenol,cc2d33" },
{ .compatible = "amphenol,cc2d33s" },
{ .compatible = "amphenol,cc2d35" },
{ .compatible = "amphenol,cc2d35s" },
{ },
};
MODULE_DEVICE_TABLE(of, cc2_of_match);
static struct i2c_driver cc2_driver = {
.driver = {
.name = "cc2d23",
.of_match_table = cc2_of_match,
},
.probe = cc2_probe,
.remove = cc2_remove,
.id_table = cc2_id,
};
module_i2c_driver(cc2_driver);
MODULE_AUTHOR("Javier Carrasco <javier.carrasco.cruz@gamil.com>");
MODULE_DESCRIPTION("Amphenol ChipCap 2 humidity and temperature sensor driver");
MODULE_LICENSE("GPL");