| // 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> | |
| #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; | |
| 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, ®_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, ®_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); | |
| switch (type) { | |
| case hwmon_temp: | |
| return cc2_measurement(data, type, val); | |
| case hwmon_humidity: | |
| switch (attr) { | |
| case hwmon_humidity_input: | |
| return cc2_measurement(data, type, val); | |
| case hwmon_humidity_min: | |
| return cc2_get_reg_val(data, CC2_R_ALARM_L_ON, val); | |
| case hwmon_humidity_min_hyst: | |
| return cc2_get_reg_val(data, CC2_R_ALARM_L_OFF, val); | |
| case hwmon_humidity_max: | |
| return cc2_get_reg_val(data, CC2_R_ALARM_H_ON, val); | |
| case hwmon_humidity_max_hyst: | |
| return cc2_get_reg_val(data, CC2_R_ALARM_H_OFF, val); | |
| case hwmon_humidity_min_alarm: | |
| return cc2_humidity_min_alarm_status(data, val); | |
| case hwmon_humidity_max_alarm: | |
| return cc2_humidity_max_alarm_status(data, val); | |
| default: | |
| return -EOPNOTSUPP; | |
| } | |
| default: | |
| return -EOPNOTSUPP; | |
| } | |
| } | |
| 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); | |
| u16 arg; | |
| u8 cmd; | |
| if (type != hwmon_humidity) | |
| return -EOPNOTSUPP; | |
| if (val < 0 || val > CC2_RH_MAX) | |
| return -EINVAL; | |
| switch (attr) { | |
| case hwmon_humidity_min: | |
| cmd = CC2_W_ALARM_L_ON; | |
| arg = cc2_rh_to_reg(val); | |
| return cc2_write_reg(data, cmd, arg); | |
| case hwmon_humidity_min_hyst: | |
| cmd = CC2_W_ALARM_L_OFF; | |
| arg = cc2_rh_to_reg(val); | |
| return cc2_write_reg(data, cmd, arg); | |
| case hwmon_humidity_max: | |
| cmd = CC2_W_ALARM_H_ON; | |
| arg = cc2_rh_to_reg(val); | |
| return cc2_write_reg(data, cmd, arg); | |
| case hwmon_humidity_max_hyst: | |
| cmd = CC2_W_ALARM_H_OFF; | |
| arg = cc2_rh_to_reg(val); | |
| return cc2_write_reg(data, cmd, arg); | |
| default: | |
| return -EOPNOTSUPP; | |
| } | |
| } | |
| 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); | |
| data->client = client; | |
| data->regulator = devm_regulator_get_exclusive(dev, "vdd"); | |
| if (IS_ERR(data->regulator)) | |
| return dev_err_probe(dev, PTR_ERR(data->regulator), | |
| "Failed to get regulator\n"); | |
| ret = cc2_request_ready_irq(data, dev); | |
| if (ret) | |
| return dev_err_probe(dev, ret, "Failed to request ready irq\n"); | |
| ret = cc2_request_alarm_irqs(data, dev); | |
| if (ret) | |
| return dev_err_probe(dev, ret, "Failed to request alarm irqs\n"); | |
| data->hwmon = devm_hwmon_device_register_with_info(dev, client->name, | |
| data, &cc2_chip_info, | |
| NULL); | |
| if (IS_ERR(data->hwmon)) | |
| return dev_err_probe(dev, PTR_ERR(data->hwmon), | |
| "Failed to register hwmon device\n"); | |
| return 0; | |
| } | |
| 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[] = { | |
| { .name = "cc2d23" }, | |
| { .name = "cc2d23s" }, | |
| { .name = "cc2d25" }, | |
| { .name = "cc2d25s" }, | |
| { .name = "cc2d33" }, | |
| { .name = "cc2d33s" }, | |
| { .name = "cc2d35" }, | |
| { .name = "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"); |