Re: [PATCH 2/2] regulator: max20339: add Maxim MAX20339 regulator driver

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



On 16/09/2024 18:48, André Draszik wrote:
> The MAX20339 is an overvoltage protection (OVP) device which also
> integrates two load switches with resistor programmable current
> limiting and includes ESD protection for the USB Type-C signal pins.
> 
> This driver exposes the main path and the two the load switches via the

...

> +
> +
> +static irqreturn_t max20339_irq(int irqno, void *data)
> +{
> +	struct max20339_irq_data *max20339 = data;
> +	struct device *dev = max20339->dev;
> +	struct regmap *regmap = max20339->regmap;
> +	u8 status[6];
> +	int ret;
> +
> +	ret = regmap_bulk_read(regmap, MAX20339_STATUS1, status,
> +			       ARRAY_SIZE(status));
> +	if (ret) {
> +		dev_err(dev, "Failed to read IRQ status: %d\n", ret);
> +		return IRQ_NONE;
> +	}
> +
> +	dev_dbg(dev,
> +		"INT1 2 3: %#.2x %#.2x %#.2x STATUS1 2 3: %#.2x %#.2x %#.2x\n",
> +		status[3], status[4], status[5], status[0], status[1],
> +		status[2]);

You should not have prints, even debugs, in interrupt handlers. This can
flood the dmesg.

> +
> +	if (!status[3] && !status[4] && !status[5])
> +		return IRQ_NONE;
> +
> +	/* overall status */
> +	if (status[3] & status[0] & MAX20339_THMFAULT) {
> +		dev_warn(dev, "Thermal fault\n");
> +		for (int i = 0; i < ARRAY_SIZE(max20339->rdevs); ++i)
> +			regulator_notifier_call_chain(max20339->rdevs[i],
> +						      REGULATOR_EVENT_OVER_TEMP,
> +						      NULL);
> +	}
> +
> +	/* INSW status */
> +	if ((status[3] & MAX20339_VINVALID)
> +	    && !(status[0] & MAX20339_VINVALID)) {
> +		dev_warn(dev, "Vin over- or undervoltage\n");

Same with all these. What happens if interrupt is triggered constantly?


> +		regulator_notifier_call_chain(max20339->rdevs[MAX20339_REGULATOR_INSW],
> +					      (REGULATOR_EVENT_UNDER_VOLTAGE_WARN
> +					       | REGULATOR_EVENT_OVER_VOLTAGE_WARN),
> +					      NULL);
> +	}
> +
> +	if (status[3] & status[0] & MAX20339_INOVFAULT) {
> +		dev_warn(dev, "Over voltage on INput\n");
> +		regulator_notifier_call_chain(max20339->rdevs[MAX20339_REGULATOR_INSW],
> +					      REGULATOR_EVENT_OVER_VOLTAGE_WARN,
> +					      NULL);
> +	}
> +


...

> +
> +static int max20339_lsw_get_error_flags(struct regulator_dev *rdev,
> +					unsigned int *flags)
> +{
> +	struct max20339_regulator *data = rdev_get_drvdata(rdev);
> +	unsigned int val;
> +	int ret;
> +
> +	ret = regmap_read(rdev_get_regmap(rdev), data->status_reg, &val);
> +	if (ret) {
> +		dev_err(rdev_get_dev(rdev),
> +			"Failed to read MAX20339_STATUS%d: %d\n",
> +			data->status_reg, ret);
> +		return ret;
> +	}
> +
> +	*flags = 0;
> +
> +	if (val & MAX20339_LSWxSHORTFAULT)
> +		*flags |= REGULATOR_ERROR_OVER_CURRENT;
> +
> +	if (val & MAX20339_LSWxOVFAULT)
> +		*flags |= REGULATOR_ERROR_OVER_VOLTAGE_WARN;
> +
> +	if (val & MAX20339_LSWxOCFAULT)
> +		*flags |= REGULATOR_ERROR_OVER_CURRENT;
> +
> +	return 0;
> +}
> +
> +static int max20339_lsw_dt_parse(struct device_node *np,
> +				 const struct regulator_desc *desc,
> +				 struct regulator_config *cfg)
> +{
> +	struct max20339_regulator *data = cfg->driver_data;
> +
> +	/* we turn missing properties into a fatal issue during probe() */

Your binding does not look in sync with above.

> +	return of_property_read_u32(np, "shunt-resistor-micro-ohms",
> +				    &data->shunt_micro_ohm);
> +}
> +
> +static const struct regulator_ops max20339_lsw_ops = {
> +	.enable = regulator_enable_regmap,
> +	.disable = regulator_disable_regmap,
> +	.is_enabled = max20339_lsw_is_enabled,
> +
> +	.set_over_current_protection = max20339_lsw_set_ocp,
> +	.set_over_voltage_protection = max20339_lsw_set_ovp,
> +
> +	.get_error_flags = max20339_lsw_get_error_flags,
> +};
> +
> +#define MAX20339_LSW_DESC(_sfx, _enable_mask, _csel_mask, _ovp_mask, _status_reg) { \
> +	.desc = {                                                 \
> +		.name = "max20339-" _sfx,                         \
> +		\
> +		.ops = &max20339_lsw_ops,                         \
> +		.type = REGULATOR_VOLTAGE,                        \
> +		.supply_name = _sfx,                              \
> +		\
> +		.enable_reg = MAX20339_SWCNTL,                    \
> +		.enable_mask = _enable_mask,                      \
> +		\
> +		.csel_reg = MAX20339_SWILIMDIV,                   \
> +		.csel_mask = _csel_mask,                          \
> +		\
> +		.regulators_node = of_match_ptr("regulators"),    \
> +		.of_match = of_match_ptr(_sfx),                   \
> +		.of_parse_cb = max20339_lsw_dt_parse,             \
> +		\
> +		.enable_time = 11000 + 1100,                      \
> +		.off_on_delay = 13,                               \
> +		.poll_enabled_time = 2420,                        \
> +		\
> +		.owner = THIS_MODULE,                             \
> +	},                                                        \
> +	.ovp_mask = _ovp_mask,                                    \
> +	.status_reg = _status_reg,                                \
> +}
> +
> +

Here and in few other places - just one blank line.

> +static struct max20339_regulator max20339_regulators[MAX20339_N_REGULATORS] = {

This can be const and then use container_of instead of rdev_get_drvdata().

See:
https://lore.kernel.org/all/20240909-regulator-const-v1-17-8934704a5787@xxxxxxxxxx/

> +	[MAX20339_REGULATOR_INSW] = {
> +		.desc = {
> +			.name = "max20339-insw",
> +
> +			.ops = &max20339_insw_ops,
> +			.type = REGULATOR_VOLTAGE,
> +			.supply_name = "insw",
> +
> +			.volt_table = max20339_insw_volt_table,
> +			.n_voltages = ARRAY_SIZE(max20339_insw_volt_table),
> +
> +			.enable_reg = MAX20339_IN_CTR,
> +			.enable_mask = MAX20339_IN_CTR_INSWEN,
> +			.enable_val = FIELD_PREP_CONST(MAX20339_IN_CTR_INSWEN,
> +						  MAX20339_IN_CTR_INSWEN_AUTO),
> +
> +			.active_discharge_reg = MAX20339_IN_CTR,
> +			.active_discharge_mask = MAX20339_IN_CTR_INDISEN,
> +			.active_discharge_on = MAX20339_IN_CTR_INDISEN,
> +			.active_discharge_off = 0,
> +
> +			.regulators_node = of_match_ptr("regulators"),
> +			.of_match = of_match_ptr("insw"),
> +
> +			.enable_time = 15000,
> +			.off_on_delay = 1,
> +			.poll_enabled_time = 3000,
> +
> +			.owner = THIS_MODULE,
> +		}
> +	},
> +	[MAX20339_REGULATOR_LSW1] = MAX20339_LSW_DESC("lsw1",
> +						MAX20339_SWCNTL_LSW1EN,
> +						MAX20339_SWILIMDIV_LSW1ILIMDIV,
> +						MAX20339_SWCNTL_LSW1OVEN,
> +						MAX20339_STATUS2),
> +	[MAX20339_REGULATOR_LSW2] = MAX20339_LSW_DESC("lsw2",
> +						MAX20339_SWCNTL_LSW2EN,
> +						MAX20339_SWILIMDIV_LSW2ILIMDIV,
> +						MAX20339_SWCNTL_LSW2OVEN,
> +						MAX20339_STATUS3),
> +};
> +
> +static int max20339_setup_irq(struct i2c_client *client,
> +			      struct regmap *regmap,
> +			      struct regulator_dev *rdevs[])
> +{
> +	u8 enabled_irqs[3];
> +	struct max20339_irq_data *max20339;
> +	int ret;
> +	unsigned long irq_flags;
> +
> +	/* the IRQ is optional */
> +	if (!client->irq) {
> +		enabled_irqs[0] = enabled_irqs[1] = enabled_irqs[2] = 0;
> +	} else {
> +		enabled_irqs[0] = (MAX20339_VINVALID | MAX20339_THMFAULT
> +				   | MAX20339_INOVFAULT);
> +		enabled_irqs[1] = (MAX20339_LSWxSHORTFAULT
> +				   | MAX20339_LSWxOVFAULT
> +				   | MAX20339_LSWxOCFAULT);
> +		enabled_irqs[2] = (MAX20339_LSWxSHORTFAULT
> +				   | MAX20339_LSWxOVFAULT
> +				   | MAX20339_LSWxOCFAULT);
> +
> +		max20339 = devm_kzalloc(&client->dev, sizeof(*max20339),
> +					GFP_KERNEL);
> +		if (!max20339)
> +			return -ENOMEM;
> +	}
> +
> +	ret = regmap_bulk_write(regmap, MAX20339_INTMASK1,
> +				enabled_irqs, ARRAY_SIZE(enabled_irqs));
> +	if (ret)
> +		return dev_err_probe(&client->dev, ret,
> +				     "error configuring INTMASK1..3\n");
> +
> +	if (!client->irq)
> +		return 0;
> +
> +	max20339->dev = &client->dev;
> +	max20339->regmap = regmap;
> +	memcpy(max20339->rdevs, rdevs, sizeof(max20339->rdevs));
> +
> +	irq_flags = IRQF_ONESHOT | IRQF_SHARED;

Why shared?

> +	irq_flags |= irqd_get_trigger_type(irq_get_irq_data(client->irq));
> +
> +	ret = devm_request_threaded_irq(&client->dev, client->irq,

Shared interrupts should not be devm. It leads to tricky cases during
removal. If you investigated the code and you are 100% sure there is no
issue, please write a short comment in the code confirming that. Or just
don't use devm.

> +					NULL, max20339_irq, irq_flags,
> +					"max20339", max20339);
> +	if (ret)
> +		return dev_err_probe(&client->dev, ret,
> +				     "IRQ setup failed\n");
> +
> +	return 0;
> +}
> +
> +#if IS_ENABLED(CONFIG_GPIO_REGMAP)
> +static int max20339_gpio_regmap_xlate(struct gpio_regmap *const gpio,
> +				      unsigned int base, unsigned int offset,
> +				      unsigned int *const reg,
> +				      unsigned int *const mask)
> +{
> +	if (offset != 0)
> +		return -EINVAL;
> +
> +	*reg = base;
> +	*mask = MAX20339_VINVALID;
> +	return 0;
> +}
> +
> +static int max20339_setup_gpio(struct i2c_client *client,
> +			       struct regmap *regmap)
> +{
> +	struct fwnode_handle *fwnode;
> +	static const char * const names[] = { "vin" };
> +	int ret;
> +
> +	fwnode = gpiochip_node_get_first(&client->dev);
> +	if (!fwnode) {
> +		dev_info(&client->dev, "Skipping gpio chip initialization\n");
> +		return 0;
> +	}
> +
> +	ret = PTR_ERR_OR_ZERO(devm_gpio_regmap_register(&client->dev,
> +			&(struct gpio_regmap_config) {
> +				.parent = &client->dev,
> +				.regmap = regmap,
> +				.fwnode = fwnode,
> +				.ngpio = ARRAY_SIZE(names),
> +				.names = names,
> +				.reg_dat_base = MAX20339_STATUS1,
> +				.reg_mask_xlate = max20339_gpio_regmap_xlate
> +			}));

That's not really readable. Please split PTR_ERR_OR_ZERO.

> +	fwnode_handle_put(fwnode);
> +	if (ret)
> +		return dev_err_probe(&client->dev, ret,
> +				     "failed to initialize gpio chip\n");
> +
> +	return 0;
> +}
> +#else /* CONFIG_GPIO_REGMAP */
> +static int max20339_setup_gpio(struct i2c_client *client,
> +			       struct regmap *regmap)
> +{
> +	return 0;
> +}
> +#endif /* CONFIG_GPIO_REGMAP */
> +
> +static int max20339_probe(struct i2c_client *client)
> +{
> +	int ret;
> +	struct regmap *regmap;
> +	struct regulator_config config = {};
> +	struct regulator_dev *rdev;
> +	struct regulator_dev *rdevs[MAX20339_N_REGULATORS];
> +
> +	/*
> +	 * either "dig-supply" is needed, or alternatively "in-supply" will
> +	 * supply power
> +	 */
> +	ret = devm_regulator_get_enable_optional(&client->dev, "dig");
> +	if (ret) {
> +		if (ret == -ENODEV)
> +			ret = devm_regulator_get_enable_optional(&client->dev,
> +								 "insw");
> +		if (ret)
> +			return dev_err_probe(&client->dev, ret,
> +					     "failed to get regulator");
> +	}
> +
> +	regmap = devm_regmap_init_i2c(client, &max20339_regmap_config);
> +	if (IS_ERR(regmap)) {
> +		dev_err_probe(&client->dev, PTR_ERR(regmap),

return dev_err_probe

> +			      "regmap init failed\n");
> +		return PTR_ERR(regmap);
> +	}
> +
> +	for (int i = 0; i < ARRAY_SIZE(max20339_regulators); ++i) {
> +		config.dev = &client->dev;
> +		config.regmap = regmap;
> +		config.driver_data = &max20339_regulators[i];
> +
> +		rdev = devm_regulator_register(config.dev,
> +					       &max20339_regulators[i].desc,
> +					       &config);
> +		if (IS_ERR(rdev))
> +			return dev_err_probe(&client->dev, PTR_ERR(rdev),
> +					     "failed to register MAX20339 regulator %s\n",
> +					     max20339_regulators[i].desc.name);
> +
> +		/*
> +		 * For the LSWs, we really need to know the shunts' values
> +		 * (from DT). Fail if missing.
> +		 */
> +		if (max20339_regulators[i].desc.csel_mask
> +		    && !max20339_regulators[i].shunt_micro_ohm)
> +			return dev_err_probe(&client->dev, -EINVAL,
> +					     "property 'shunt-resistor-micro-ohms' not found\n");
> +
> +		rdevs[i] = rdev;
> +
> +		dev_info(&client->dev, "registered MAX20339 regulator %s\n",
> +			 max20339_regulators[i].desc.name);
> +	}
> +
> +	ret = max20339_setup_irq(client, regmap, rdevs);
> +	if (ret < 0)
> +		return ret;
> +
> +	return max20339_setup_gpio(client, regmap);
> +}
> +
> +static const struct i2c_device_id max20339_i2c_id[] = {
> +	{ "max20339", 0 },
> +	{ }
> +};
> +MODULE_DEVICE_TABLE(i2c, max20339_i2c_id);
> +
> +#ifdef CONFIG_OF
> +static const struct of_device_id max20339_of_id[] = {
> +	{ .compatible = "maxim,max20339", },
> +	{},
> +};
> +MODULE_DEVICE_TABLE(of, max20339_of_id);
> +#endif
> +
> +static struct i2c_driver max20339_i2c_driver = {
> +	.driver = {
> +		.name = "max20339",
> +		.probe_type = PROBE_PREFER_ASYNCHRONOUS,
> +		.of_match_table = of_match_ptr(max20339_of_id),

Drop of_match_ptr and earlier #ifdef. Not much benefits and limits usage
to OF-systems.

Best regards,
Krzysztof





[Index of Archives]     [Device Tree Compilter]     [Device Tree Spec]     [Linux Driver Backports]     [Video for Linux]     [Linux USB Devel]     [Linux PCI Devel]     [Linux Audio Users]     [Linux Kernel]     [Linux SCSI]     [XFree86]     [Yosemite Backpacking]


  Powered by Linux