793 lines · 18.3 KB · C
RawDownload
// 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/cleanup.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>#include <linux/mod_devicetable.h>#include <linux/property.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 regulator *regulator;	const char *name;	const char *label;	int irq_ready;	int irq_low;	int irq_high;	bool process_irqs;}; /* %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_label:			return cc2->label ? 0444 : 0;		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;		case hwmon_temp_label:			return cc2->label ? 0444 : 0;		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, 0);		cc2->rh_alarm.low_alarm = true;	} 

Lines 1–500 of 793.