diff options
Diffstat (limited to 'drivers/hwmon/tmp421.c')
| -rw-r--r-- | drivers/hwmon/tmp421.c | 186 | 
1 files changed, 170 insertions, 16 deletions
| diff --git a/drivers/hwmon/tmp421.c b/drivers/hwmon/tmp421.c index b963a369c5ab..1fd8d41d90c8 100644 --- a/drivers/hwmon/tmp421.c +++ b/drivers/hwmon/tmp421.c @@ -29,15 +29,20 @@ static const unsigned short normal_i2c[] = { 0x2a, 0x4c, 0x4d, 0x4e, 0x4f,  enum chips { tmp421, tmp422, tmp423, tmp441, tmp442 }; +#define MAX_CHANNELS				4  /* The TMP421 registers */  #define TMP421_STATUS_REG			0x08  #define TMP421_CONFIG_REG_1			0x09 +#define TMP421_CONFIG_REG_2			0x0A +#define TMP421_CONFIG_REG_REN(x)		(BIT(3 + (x))) +#define TMP421_CONFIG_REG_REN_MASK		GENMASK(6, 3)  #define TMP421_CONVERSION_RATE_REG		0x0B +#define TMP421_N_FACTOR_REG_1			0x21  #define TMP421_MANUFACTURER_ID_REG		0xFE  #define TMP421_DEVICE_ID_REG			0xFF -static const u8 TMP421_TEMP_MSB[4]		= { 0x00, 0x01, 0x02, 0x03 }; -static const u8 TMP421_TEMP_LSB[4]		= { 0x10, 0x11, 0x12, 0x13 }; +static const u8 TMP421_TEMP_MSB[MAX_CHANNELS]	= { 0x00, 0x01, 0x02, 0x03 }; +static const u8 TMP421_TEMP_LSB[MAX_CHANNELS]	= { 0x10, 0x11, 0x12, 0x13 };  /* Flags */  #define TMP421_CONFIG_SHUTDOWN			0x40 @@ -86,18 +91,24 @@ static const struct of_device_id __maybe_unused tmp421_of_match[] = {  };  MODULE_DEVICE_TABLE(of, tmp421_of_match); +struct tmp421_channel { +	const char *label; +	bool enabled; +	s16 temp; +}; +  struct tmp421_data {  	struct i2c_client *client;  	struct mutex update_lock; -	u32 temp_config[5]; +	u32 temp_config[MAX_CHANNELS + 1];  	struct hwmon_channel_info temp_info;  	const struct hwmon_channel_info *info[2];  	struct hwmon_chip_info chip; -	char valid; +	bool valid;  	unsigned long last_updated;  	unsigned long channels;  	u8 config; -	s16 temp[4]; +	struct tmp421_channel channel[MAX_CHANNELS];  };  static int temp_from_raw(u16 reg, bool extended) @@ -132,28 +143,56 @@ static int tmp421_update_device(struct tmp421_data *data)  			ret = i2c_smbus_read_byte_data(client, TMP421_TEMP_MSB[i]);  			if (ret < 0)  				goto exit; -			data->temp[i] = ret << 8; +			data->channel[i].temp = ret << 8;  			ret = i2c_smbus_read_byte_data(client, TMP421_TEMP_LSB[i]);  			if (ret < 0)  				goto exit; -			data->temp[i] |= ret; +			data->channel[i].temp |= ret;  		}  		data->last_updated = jiffies; -		data->valid = 1; +		data->valid = true;  	}  exit:  	mutex_unlock(&data->update_lock);  	if (ret < 0) { -		data->valid = 0; +		data->valid = false;  		return ret;  	}  	return 0;  } +static int tmp421_enable_channels(struct tmp421_data *data) +{ +	int err; +	struct i2c_client *client = data->client; +	struct device *dev = &client->dev; +	int old = i2c_smbus_read_byte_data(client, TMP421_CONFIG_REG_2); +	int new, i; + +	if (old < 0) { +		dev_err(dev, "error reading register, can't disable channels\n"); +		return old; +	} + +	new = old & ~TMP421_CONFIG_REG_REN_MASK; +	for (i = 0; i < data->channels; i++) +		if (data->channel[i].enabled) +			new |= TMP421_CONFIG_REG_REN(i); + +	if (new == old) +		return 0; + +	err = i2c_smbus_write_byte_data(client, TMP421_CONFIG_REG_2, new); +	if (err < 0) +		dev_err(dev, "error writing register, can't disable channels\n"); + +	return err; +} +  static int tmp421_read(struct device *dev, enum hwmon_sensor_types type,  		       u32 attr, int channel, long *val)  { @@ -166,15 +205,22 @@ static int tmp421_read(struct device *dev, enum hwmon_sensor_types type,  	switch (attr) {  	case hwmon_temp_input: -		*val = temp_from_raw(tmp421->temp[channel], +		if (!tmp421->channel[channel].enabled) +			return -ENODATA; +		*val = temp_from_raw(tmp421->channel[channel].temp,  				     tmp421->config & TMP421_CONFIG_RANGE);  		return 0;  	case hwmon_temp_fault: +		if (!tmp421->channel[channel].enabled) +			return -ENODATA;  		/*  		 * Any of OPEN or /PVLD bits indicate a hardware mulfunction  		 * and the conversion result may be incorrect  		 */ -		*val = !!(tmp421->temp[channel] & 0x03); +		*val = !!(tmp421->channel[channel].temp & 0x03); +		return 0; +	case hwmon_temp_enable: +		*val = tmp421->channel[channel].enabled;  		return 0;  	default:  		return -EOPNOTSUPP; @@ -182,6 +228,34 @@ static int tmp421_read(struct device *dev, enum hwmon_sensor_types type,  } +static int tmp421_read_string(struct device *dev, enum hwmon_sensor_types type, +			     u32 attr, int channel, const char **str) +{ +	struct tmp421_data *data = dev_get_drvdata(dev); + +	*str = data->channel[channel].label; + +	return 0; +} + +static int tmp421_write(struct device *dev, enum hwmon_sensor_types type, +			u32 attr, int channel, long val) +{ +	struct tmp421_data *data = dev_get_drvdata(dev); +	int ret; + +	switch (attr) { +	case hwmon_temp_enable: +		data->channel[channel].enabled = val; +		ret = tmp421_enable_channels(data); +		break; +	default: +	    ret = -EOPNOTSUPP; +	} + +	return ret; +} +  static umode_t tmp421_is_visible(const void *data, enum hwmon_sensor_types type,  				 u32 attr, int channel)  { @@ -189,14 +263,19 @@ static umode_t tmp421_is_visible(const void *data, enum hwmon_sensor_types type,  	case hwmon_temp_fault:  	case hwmon_temp_input:  		return 0444; +	case hwmon_temp_label: +		return 0444; +	case hwmon_temp_enable: +		return 0644;  	default:  		return 0;  	}  } -static int tmp421_init_client(struct i2c_client *client) +static int tmp421_init_client(struct tmp421_data *data)  {  	int config, config_orig; +	struct i2c_client *client = data->client;  	/* Set the conversion rate to 2 Hz */  	i2c_smbus_write_byte_data(client, TMP421_CONVERSION_RATE_REG, 0x05); @@ -217,7 +296,7 @@ static int tmp421_init_client(struct i2c_client *client)  		i2c_smbus_write_byte_data(client, TMP421_CONFIG_REG_1, config);  	} -	return 0; +	return tmp421_enable_channels(data);  }  static int tmp421_detect(struct i2c_client *client, @@ -281,9 +360,78 @@ static int tmp421_detect(struct i2c_client *client,  	return 0;  } +static int tmp421_probe_child_from_dt(struct i2c_client *client, +				      struct device_node *child, +				      struct tmp421_data *data) + +{ +	struct device *dev = &client->dev; +	u32 i; +	s32 val; +	int err; + +	err = of_property_read_u32(child, "reg", &i); +	if (err) { +		dev_err(dev, "missing reg property of %pOFn\n", child); +		return err; +	} + +	if (i >= data->channels) { +		dev_err(dev, "invalid reg %d of %pOFn\n", i, child); +		return -EINVAL; +	} + +	of_property_read_string(child, "label", &data->channel[i].label); +	if (data->channel[i].label) +		data->temp_config[i] |= HWMON_T_LABEL; + +	data->channel[i].enabled = of_device_is_available(child); + +	err = of_property_read_s32(child, "ti,n-factor", &val); +	if (!err) { +		if (i == 0) { +			dev_err(dev, "n-factor can't be set for internal channel\n"); +			return -EINVAL; +		} + +		if (val > 127 || val < -128) { +			dev_err(dev, "n-factor for channel %d invalid (%d)\n", +				i, val); +			return -EINVAL; +		} +		i2c_smbus_write_byte_data(client, TMP421_N_FACTOR_REG_1 + i - 1, +					  val); +	} + +	return 0; +} + +static int tmp421_probe_from_dt(struct i2c_client *client, struct tmp421_data *data) +{ +	struct device *dev = &client->dev; +	const struct device_node *np = dev->of_node; +	struct device_node *child; +	int err; + +	for_each_child_of_node(np, child) { +		if (strcmp(child->name, "channel")) +			continue; + +		err = tmp421_probe_child_from_dt(client, child, data); +		if (err) { +			of_node_put(child); +			return err; +		} +	} + +	return 0; +} +  static const struct hwmon_ops tmp421_ops = {  	.is_visible = tmp421_is_visible,  	.read = tmp421_read, +	.read_string = tmp421_read_string, +	.write = tmp421_write,  };  static int tmp421_probe(struct i2c_client *client) @@ -305,12 +453,18 @@ static int tmp421_probe(struct i2c_client *client)  		data->channels = i2c_match_id(tmp421_id, client)->driver_data;  	data->client = client; -	err = tmp421_init_client(client); +	for (i = 0; i < data->channels; i++) { +		data->temp_config[i] = HWMON_T_INPUT | HWMON_T_FAULT | HWMON_T_ENABLE; +		data->channel[i].enabled = true; +	} + +	err = tmp421_probe_from_dt(client, data);  	if (err)  		return err; -	for (i = 0; i < data->channels; i++) -		data->temp_config[i] = HWMON_T_INPUT | HWMON_T_FAULT; +	err = tmp421_init_client(data); +	if (err) +		return err;  	data->chip.ops = &tmp421_ops;  	data->chip.info = data->info; | 
