[<prev] [next>] [<thread-prev] [day] [month] [year] [list]
Message-ID: <1420d635-5d12-402f-91c6-12cb79304352@roeck-us.net>
Date: Fri, 21 Nov 2025 11:41:12 -0800
From: Guenter Roeck <linux@...ck-us.net>
To: victor.duicu@...rochip.com, robh@...nel.org, krzk+dt@...nel.org,
conor+dt@...nel.org, corbet@....net
Cc: marius.cristea@...rochip.com, linux-hwmon@...r.kernel.org,
linux-kernel@...r.kernel.org, devicetree@...r.kernel.org,
linux-doc@...r.kernel.org
Subject: Re: [PATCH v2 2/2] hwmon: add support for MCP998X
On 11/19/25 23:12, victor.duicu@...rochip.com wrote:
> From: Victor Duicu <victor.duicu@...rochip.com>
>
> This is the driver for Microchip MCP998X/33 and MCP998XD/33D
> Multichannel Automotive Temperature Monitor Family.
>
> Signed-off-by: Victor Duicu <victor.duicu@...rochip.com>
> ---
...
> + if (priv->run_state) {
> + if (priv->wait_before_read) {
> + if (!time_after(jiffies, priv->time_limit))
> + mdelay(jiffies_to_msecs(priv->time_limit - jiffies));
mdelay (busy wait) is kind of brutal.
> + priv->wait_before_read = false;
> + }
> + } else {
> + ret = regmap_write(priv->regmap, MCP9982_ONE_SHOT_ADDR, 1);
> + if (ret)
> + return ret;
> +
> + /*
> + * In Standby state after writing in OneShot register wait for
> + * the start of conversion and then poll the BUSY bit.
> + */
> + mdelay(MCP9982_WAKE_UP_TIME_MS);
Even more so here, where the busy wait is a whopping 125 ms. Please either use
usleep_range() or explain in detail why the busy wait is necessary. I can not really
imagine a situation where the power savings of running in OneShot mode would outweigh
125 ms of active CPU time.
Guenter
> + ret = regmap_read_poll_timeout(priv->regmap, MCP9982_STATUS_ADDR,
> + reg_status, !(reg_status & MCP9982_STATUS_BUSY),
> + (mcp9982_update_interval[priv->interval_idx]) *
> + USEC_PER_MSEC, 0);
This is puzzling: There is no final timeout, meaning if the chip is stuck for some reason
the code will be stuck as well. At the same time, the code _will_ sleep here for a full
update interval befor echecking the status.
All this warrants a detailed explanation.
> + if (ret)
> + return ret;
> + }
> +
> + switch (type) {
> + case hwmon_temp:
> + switch (attr) {
> + case hwmon_temp_input:
> + /*
> + * The Block Read Protocol first returns the number of user readable
> + * bytes, held in buff[0], followed by the data.
> + */
> + ret = regmap_bulk_read(priv->regmap, MCP9982_TEMP_MEM_BLOCK_ADDR(channel),
> + &buff, sizeof(buff));
> + if (ret)
> + return ret;
> +
> + *val = get_unaligned_be16(buff + 1) >> 5;
> + *val = (*val - (MCP9982_OFFSET << 3)) * 125;
> +
> + return 0;
> + case hwmon_temp_max:
> + if (channel)
> + addr = MCP9982_EXT_HIGH_LIMIT_ADDR(channel);
> + else
> + addr = MCP9982_INTERNAL_HIGH_LIMIT_ADDR;
> +
> + return mcp9982_read_limit(priv, addr, val);
> + case hwmon_temp_max_alarm:
> + mask = BIT(channel);
> + *val = regmap_test_bits(priv->regmap, MCP9982_HIGH_LIMIT_STATUS_ADDR, mask);
> + if (*val < 0)
> + return *val;
> +
> + return 0;
> + case hwmon_temp_min:
> + if (channel)
> + addr = MCP9982_EXT_LOW_LIMIT_ADDR(channel);
> + else
> + addr = MCP9982_INTERNAL_LOW_LIMIT_ADDR;
> +
> + return mcp9982_read_limit(priv, addr, val);
> + case hwmon_temp_min_alarm:
> + mask = BIT(channel);
> + *val = regmap_test_bits(priv->regmap, MCP9982_LOW_LIMIT_STATUS_ADDR, mask);
> + if (*val < 0)
> + return *val;
> +
> + return 0;
> + case hwmon_temp_crit:
> + return mcp9982_read_limit(priv, MCP9982_THERM_LIMIT_ADDR(channel), val);
> + case hwmon_temp_crit_alarm:
> + mask = BIT(channel);
> + *val = regmap_test_bits(priv->regmap, MCP9982_THERM_LIMIT_STATUS_ADDR,
> + mask);
> + if (*val < 0)
> + return *val;
> +
> + return 0;
> + default:
> + return -EINVAL;
> + }
> + case hwmon_chip:
> + switch (attr) {
> + case hwmon_chip_update_interval:
> + *val = mcp9982_update_interval[priv->interval_idx];
> + return 0;
> + default:
> + return -EINVAL;
> + }
> + default:
> + return -EINVAL;
> + }
> +}
> +
> +static int mcp9982_read_label(struct device *dev, enum hwmon_sensor_types type, u32 attr,
> + int channel, const char **str)
> +{
> + struct mcp9982_priv *priv = dev_get_drvdata(dev);
> +
> + switch (type) {
> + case hwmon_temp:
> + switch (attr) {
> + case hwmon_temp_label:
> + if (priv->labels[channel]) {
> + *str = priv->labels[channel];
> + return 0;
> + } else {
> + return -EOPNOTSUPP;
> + }
> + default:
> + return -EOPNOTSUPP;
> + }
> + default:
> + return -EOPNOTSUPP;
> + }
> +}
> +
> +static int mcp9982_write_limit(struct mcp9982_priv *priv, u8 address, long val)
> +{
> + int ret;
> +
> + /* Range is always -64 to 191.875°C. */
> + val = clamp_val(val, -64000, 191875);
> + val = (val + MCP9982_OFFSET * 1000) << 5;
> + val = DIV_ROUND_CLOSEST(val, 125);
> +
> + switch (address) {
> + case MCP9982_INTERNAL_HIGH_LIMIT_ADDR:
> + case MCP9982_INTERNAL_LOW_LIMIT_ADDR:
> + case MCP9982_INTERNAL_THERM_LIMIT_ADDR:
> + case MCP9982_EXT_1_THERM_LIMIT_ADDR:
> + case MCP9982_EXT_2_THERM_LIMIT_ADDR:
> + case MCP9982_EXT_3_THERM_LIMIT_ADDR:
> + case MCP9982_EXT_4_THERM_LIMIT_ADDR:
> + val = val >> 8;
> + ret = regmap_write(priv->regmap, address, val);
> + if (ret)
> + return ret;
> +
> + return 0;
> + default:
> + put_unaligned_be16(val, &val);
What is this supposed to do ? Trying to avoid a local 2-byte buffer ?
> + ret = regmap_bulk_write(priv->regmap, address, &val, 2);
> + if (ret)
> + return ret;
> +
> + return 0;
> + }
> +}
> +
> +static int mcp9982_write(struct device *dev, enum hwmon_sensor_types type, u32 attr, int channel,
> + long val)
> +{
> + struct mcp9982_priv *priv = dev_get_drvdata(dev);
> + unsigned int previous_interval_idx, idx;
> + bool use_previous_interval = false;
> + unsigned long new_time_limit;
> + u8 addr;
> + int ret;
> +
> + switch (type) {
> + case hwmon_chip:
> + switch (attr) {
> + case hwmon_chip_update_interval:
> + previous_interval_idx = priv->interval_idx;
> +
> + /*
> + * For MCP998XD and MCP9933D update interval
> + * can't be slower than 1 second.
> + */
> + if (priv->chip->hw_thermal_shutdown)
> + val = clamp(val, 0, 1000);
> +
> + idx = find_closest_descending(val, mcp9982_update_interval,
> + ARRAY_SIZE(mcp9982_update_interval));
> +
> + ret = regmap_write(priv->regmap, MCP9982_CONV_ADDR, idx);
> + if (ret)
> + return ret;
> +
> + priv->interval_idx = idx;
> +
> + /*
> + * When changing the interval time in Run mode, wait a delay based
> + * on the previous value to ensure the new value becomes active.
> + */
> + if (priv->run_state)
> + use_previous_interval = true;
> + else
> + return 0;
> + break;
> + default:
> + return -EINVAL;
> + }
> + break;
> + case hwmon_temp:
> + switch (attr) {
> + case hwmon_temp_max:
> + if (channel)
> + addr = MCP9982_EXT_HIGH_LIMIT_ADDR(channel);
> + else
> + addr = MCP9982_INTERNAL_HIGH_LIMIT_ADDR;
> +
> + return ret = mcp9982_write_limit(priv, addr, val);
> + case hwmon_temp_min:
> + if (channel)
> + addr = MCP9982_EXT_LOW_LIMIT_ADDR(channel);
> + else
> + addr = MCP9982_INTERNAL_LOW_LIMIT_ADDR;
> +
> + return mcp9982_write_limit(priv, addr, val);
> + case hwmon_temp_crit:
> + return mcp9982_write_limit(priv, MCP9982_THERM_LIMIT_ADDR(channel), val);
> + default:
> + return -EINVAL;
> + }
> + default:
> + return -EINVAL;
> + }
> +
> + /*
> + * Calculate the time point when it would be safe to read after
> + * the current write operation in Run mode.
> + * If, for example, we change update interval from a slower time
> + * to a faster time, the change will become active after the
> + * conversion with the slower time is finished. If we read before
> + * the end of conversion, the value will be from the previous cycle.
> + */
> + if (use_previous_interval) {
> + new_time_limit = msecs_to_jiffies(mcp9982_update_interval[previous_interval_idx]);
> + use_previous_interval = false;
> + } else {
> + new_time_limit = msecs_to_jiffies(mcp9982_update_interval[priv->interval_idx]);
> + }
> +
> + new_time_limit += jiffies + msecs_to_jiffies(MCP9982_CONVERSION_TIME_MS);
> +
> + if (time_after(new_time_limit, priv->time_limit)) {
> + priv->time_limit = new_time_limit;
> + priv->wait_before_read = true;
> + }
> +
> + return 0;
> +}
> +
> +static umode_t mcp9982_is_visible(const void *_data, enum hwmon_sensor_types type, u32 attr,
> + int channel)
> +{
> + const struct mcp9982_priv *priv = _data;
> +
> + if (!test_bit(channel, &priv->enabled_channel_mask))
> + return 0;
> +
> + switch (type) {
> + case hwmon_temp:
> + switch (attr) {
> + case hwmon_temp_label:
> + if (priv->labels[channel])
> + return 0444;
> + else
> + return 0;
> + case hwmon_temp_input:
> + case hwmon_temp_min_alarm:
> + case hwmon_temp_max_alarm:
> + case hwmon_temp_crit_alarm:
> + return 0444;
> + case hwmon_temp_min:
> + case hwmon_temp_max:
> + case hwmon_temp_crit:
> + return 0644;
> + default:
> + return 0;
> + }
> + case hwmon_chip:
> + switch (attr) {
> + case hwmon_chip_update_interval:
> + return 0644;
> + default:
> + return 0;
> + }
> + default:
> + return 0;
> + }
> +}
> +
> +static const struct hwmon_ops mcp9982_hwmon_ops = {
> + .is_visible = mcp9982_is_visible,
> + .read = mcp9982_read,
> + .read_string = mcp9982_read_label,
> + .write = mcp9982_write,
> +};
> +
> +static int mcp9982_init(struct device *dev, struct mcp9982_priv *priv)
> +{
> + unsigned int i;
> + int ret;
> + u8 val;
> +
> + /* Chips 82/83 and 82D/83D do not support anti-parallel diode mode. */
> + if (!priv->chip->allow_apdd && priv->apdd_enable == 1)
> + return dev_err_probe(dev, -EINVAL, "Incorrect setting of APDD.\n");
> +
> + /* Chips with "D" work only in Run state. */
> + if (priv->chip->hw_thermal_shutdown && !priv->run_state)
> + return dev_err_probe(dev, -EINVAL, "Incorrect setting of Power State.\n");
> +
> + /*
> + * For chips with "D" in the name, resistance error correction must be on
> + * so that hardware shutdown feature can't be overridden.
> + */
> + if (priv->chip->hw_thermal_shutdown)
> + if (!priv->recd34_enable || !priv->recd12_enable)
> + return dev_err_probe(dev, -EINVAL, "Incorrect setting of RECD.\n");
> +
> + /*
> + * Set default values in registers.
> + * APDD, RECD12 and RECD34 are active on 0.
> + */
> + val = FIELD_PREP(MCP9982_CFG_MSKAL, 1) |
> + FIELD_PREP(MCP9982_CFG_RS, !priv->run_state) |
> + FIELD_PREP(MCP9982_CFG_ATTHM, 1) |
> + FIELD_PREP(MCP9982_CFG_RECD12, !priv->recd12_enable) |
> + FIELD_PREP(MCP9982_CFG_RECD34, !priv->recd34_enable) |
> + FIELD_PREP(MCP9982_CFG_RANGE, 1) | FIELD_PREP(MCP9982_CFG_DA_ENA, 0) |
> + FIELD_PREP(MCP9982_CFG_APDD, !priv->apdd_enable);
> +
> + ret = regmap_write(priv->regmap, MCP9982_CFG_ADDR, val);
> + if (ret)
> + return ret;
> +
> + ret = regmap_write(priv->regmap, MCP9982_CONV_ADDR, MCP9982_DEFAULT_CONV_VAL);
> + if (ret)
> + return ret;
> + priv->interval_idx = MCP9982_DEFAULT_CONV_VAL;
> +
> + ret = regmap_write(priv->regmap, MCP9982_HYS_ADDR, MCP9982_DEFAULT_HYS_VAL);
> + if (ret)
> + return ret;
> +
> + ret = regmap_write(priv->regmap, MCP9982_CONSEC_ALRT_ADDR, MCP9982_DEFAULT_CONSEC_ALRT_VAL);
> + if (ret)
> + return ret;
> +
> + ret = regmap_write(priv->regmap, MCP9982_RUNNING_AVG_ADDR, 0);
> + if (ret)
> + return ret;
> +
> + ret = regmap_write(priv->regmap, MCP9982_HOTTEST_CFG_ADDR, 0);
> + if (ret)
> + return ret;
> +
> + /*
> + * Only external channels 1 and 2 support beta compensation.
> + * Set beta auto-detection.
> + */
> + for (i = 1; i < 3; i++)
> + if (test_bit(i, &priv->enabled_channel_mask)) {
> + ret = regmap_write(priv->regmap, MCP9982_EXT_BETA_CFG_ADDR(i),
> + MCP9982_BETA_AUTODETECT);
> + if (ret)
> + return ret;
> + }
> +
> + /* Set default values for internal channel limits. */
> + if (test_bit(0, &priv->enabled_channel_mask)) {
> + ret = mcp9982_write_limit(priv, MCP9982_INTERNAL_HIGH_LIMIT_ADDR,
> + MCP9982_HIGH_LIMIT_DEFAULT);
> + if (ret)
> + return ret;
> +
> + ret = mcp9982_write_limit(priv, MCP9982_INTERNAL_LOW_LIMIT_ADDR,
> + MCP9982_LOW_LIMIT_DEFAULT);
> + if (ret)
> + return ret;
> +
> + ret = mcp9982_write_limit(priv, MCP9982_THERM_LIMIT_ADDR(0),
> + MCP9982_HIGH_LIMIT_DEFAULT);
> + if (ret)
> + return ret;
> + }
> +
> + /* Set ideality factor and limits to default for external channels. */
> + for (i = 1; i < MCP9982_MAX_NUM_CHANNELS; i++)
> + if (test_bit(i, &priv->enabled_channel_mask)) {
> + ret = regmap_write(priv->regmap, MCP9982_EXT_IDEAL_ADDR(i),
> + MCP9982_IDEALITY_DEFAULT);
> + if (ret)
> + return ret;
> +
> + ret = mcp9982_write_limit(priv, MCP9982_EXT_HIGH_LIMIT_ADDR(i),
> + MCP9982_HIGH_LIMIT_DEFAULT);
> + if (ret)
> + return ret;
> +
> + ret = mcp9982_write_limit(priv, MCP9982_EXT_LOW_LIMIT_ADDR(i),
> + MCP9982_LOW_LIMIT_DEFAULT);
> + if (ret)
> + return ret;
> +
> + ret = mcp9982_write_limit(priv, MCP9982_THERM_LIMIT_ADDR(i),
> + MCP9982_HIGH_LIMIT_DEFAULT);
> + if (ret)
> + return ret;
> + }
> +
> + priv->wait_before_read = false;
> + priv->time_limit = jiffies;
> +
> + return 0;
> +}
> +
> +static int mcp9982_parse_fw_config(struct device *dev, int device_nr_channels)
> +{
> + unsigned int reg_nr;
> + struct mcp9982_priv *priv = dev_get_drvdata(dev);
> + int ret;
> +
> + /* Initialise internal channel( which is always present ). */
> + priv->labels[0] = "internal diode";
> + priv->enabled_channel_mask = 1;
> +
> + /* Default values to work on systems without devicetree or firmware nodes. */
> + if (!dev_fwnode(dev)) {
> + priv->num_channels = device_nr_channels;
> + priv->enabled_channel_mask = BIT(priv->num_channels) - 1;
> + priv->apdd_enable = false;
> + priv->recd12_enable = true;
> + priv->recd34_enable = true;
> + priv->run_state = true;
> + return 0;
> + }
> +
> + priv->apdd_enable =
> + device_property_read_bool(dev, "microchip,enable-anti-parallel");
> +
> + priv->recd12_enable =
> + device_property_read_bool(dev, "microchip,parasitic-res-on-channel1-2");
> +
> + priv->recd34_enable =
> + device_property_read_bool(dev, "microchip,parasitic-res-on-channel3-4");
> +
> + priv->run_state =
> + device_property_read_bool(dev, "microchip,power-state");
> +
> + priv->num_channels = device_get_child_node_count(dev) + 1;
> +
> + if (priv->num_channels > device_nr_channels)
> + return dev_err_probe(dev, -E2BIG,
> + "More channels than the chip supports.\n");
> +
> + /* Read information about the external channels. */
> + device_for_each_child_node_scoped(dev, child) {
> + reg_nr = 0;
> + ret = fwnode_property_read_u32(child, "reg", ®_nr);
> + if (ret || !reg_nr || reg_nr >= device_nr_channels)
> + return dev_err_probe(dev, -EINVAL,
> + "Channel reg is incorrectly set.\n");
> +
> + fwnode_property_read_string(child, "label", &priv->labels[reg_nr]);
> + set_bit(reg_nr, &priv->enabled_channel_mask);
> + }
> +
> + return 0;
> +}
> +
> +static int mcp9982_probe(struct i2c_client *client)
> +{
> + struct hwmon_chip_info mcp998x_chip_info;
> + const struct mcp9982_features *chip;
> + struct device *dev = &client->dev;
> + struct mcp9982_priv *priv;
> + struct device *hwmon_dev;
> + int ret;
> +
> + priv = devm_kzalloc(dev, sizeof(struct mcp9982_priv), GFP_KERNEL);
> + if (!priv)
> + return -ENOMEM;
> +
> + priv->regmap = devm_regmap_init_i2c(client, &mcp9982_regmap_config);
> +
> + if (IS_ERR(priv->regmap))
> + return dev_err_probe(dev, PTR_ERR(priv->regmap),
> + "Cannot initialize register map.\n");
> +
> + dev_set_drvdata(dev, priv);
> +
> + chip = i2c_get_match_data(client);
> + if (!chip)
> + return -EINVAL;
> + priv->chip = chip;
> +
> + ret = mcp9982_parse_fw_config(dev, chip->phys_channels);
> + if (ret)
> + return ret;
> +
> + ret = mcp9982_init(dev, priv);
> + if (ret)
> + return ret;
> +
> + mcp998x_chip_info.ops = &mcp9982_hwmon_ops;
> + mcp998x_chip_info.info = mcp9985_info;
> +
> + hwmon_dev = devm_hwmon_device_register_with_info(dev, chip->name, priv,
> + &mcp998x_chip_info, NULL);
> +
> + return PTR_ERR_OR_ZERO(hwmon_dev);
> +}
> +
> +static const struct i2c_device_id mcp9982_id[] = {
> + { .name = "mcp9933", .driver_data = (kernel_ulong_t)&mcp9933_chip_config },
> + { .name = "mcp9933d", .driver_data = (kernel_ulong_t)&mcp9933d_chip_config },
> + { .name = "mcp9982", .driver_data = (kernel_ulong_t)&mcp9982_chip_config },
> + { .name = "mcp9982d", .driver_data = (kernel_ulong_t)&mcp9982d_chip_config },
> + { .name = "mcp9983", .driver_data = (kernel_ulong_t)&mcp9983_chip_config },
> + { .name = "mcp9983d", .driver_data = (kernel_ulong_t)&mcp9983d_chip_config },
> + { .name = "mcp9984", .driver_data = (kernel_ulong_t)&mcp9984_chip_config },
> + { .name = "mcp9984d", .driver_data = (kernel_ulong_t)&mcp9984d_chip_config },
> + { .name = "mcp9985", .driver_data = (kernel_ulong_t)&mcp9985_chip_config },
> + { .name = "mcp9985d", .driver_data = (kernel_ulong_t)&mcp9985d_chip_config },
> + { }
> +};
> +MODULE_DEVICE_TABLE(i2c, mcp9982_id);
> +
> +static const struct of_device_id mcp9982_of_match[] = {
> + {
> + .compatible = "microchip,mcp9933",
> + .data = &mcp9933_chip_config,
> + }, {
> + .compatible = "microchip,mcp9933d",
> + .data = &mcp9933d_chip_config,
> + }, {
> + .compatible = "microchip,mcp9982",
> + .data = &mcp9982_chip_config,
> + }, {
> + .compatible = "microchip,mcp9982d",
> + .data = &mcp9982d_chip_config,
> + }, {
> + .compatible = "microchip,mcp9983",
> + .data = &mcp9983_chip_config,
> + }, {
> + .compatible = "microchip,mcp9983d",
> + .data = &mcp9983d_chip_config,
> + }, {
> + .compatible = "microchip,mcp9984",
> + .data = &mcp9984_chip_config,
> + }, {
> + .compatible = "microchip,mcp9984d",
> + .data = &mcp9984d_chip_config,
> + }, {
> + .compatible = "microchip,mcp9985",
> + .data = &mcp9985_chip_config,
> + }, {
> + .compatible = "microchip,mcp9985d",
> + .data = &mcp9985d_chip_config,
> + },
> + { }
> +};
> +MODULE_DEVICE_TABLE(of, mcp9982_of_match);
> +
> +static struct i2c_driver mcp9982_driver = {
> + .driver = {
> + .name = "mcp9982",
> + .of_match_table = mcp9982_of_match,
> + },
> + .probe = mcp9982_probe,
> + .id_table = mcp9982_id,
> +};
> +module_i2c_driver(mcp9982_driver);
> +
> +MODULE_AUTHOR("Victor Duicu <victor.duicu@...rochip.com>");
> +MODULE_DESCRIPTION("MCP998X/33 and MCP998XD/33D Multichannel Automotive Temperature Monitor Driver");
> +MODULE_LICENSE("GPL");
Powered by blists - more mailing lists