Skip to content

Commit bfff546

Browse files
watsonadsbroonie
authored andcommitted
regulator: Add MAX20086-MAX20089 driver
The MAX20086-MAX20089 are dual/quad power protectors for cameras. Add a driver that supports controlling the outputs individually. Additional features, such as overcurrent detection, may be added later if needed. Signed-off-by: Watson Chow <watson.chow@avnet.com> Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com> Link: https://lore.kernel.org/r/20220106224350.16957-3-laurent.pinchart+renesas@ideasonboard.com Signed-off-by: Mark Brown <broonie@kernel.org>
1 parent 764aaa4 commit bfff546

File tree

4 files changed

+349
-1
lines changed

4 files changed

+349
-1
lines changed

MAINTAINERS

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11558,6 +11558,13 @@ S: Maintained
1155811558
F: Documentation/devicetree/bindings/power/supply/maxim,max17042.yaml
1155911559
F: drivers/power/supply/max17042_battery.c
1156011560

11561+
MAXIM MAX20086 CAMERA POWER PROTECTOR DRIVER
11562+
M: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
11563+
L: linux-kernel@vger.kernel.org
11564+
S: Maintained
11565+
F: Documentation/devicetree/bindings/regulator/maxim,max20086.yaml
11566+
F: drivers/regulator/max20086-regulator.c
11567+
1156111568
MAXIM MAX77650 PMIC MFD DRIVER
1156211569
M: Bartosz Golaszewski <brgl@bgdev.pl>
1156311570
L: linux-kernel@vger.kernel.org

drivers/regulator/Kconfig

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -636,6 +636,15 @@ config REGULATOR_MAX8998
636636
via I2C bus. The provided regulator is suitable for S3C6410
637637
and S5PC1XX chips to control VCC_CORE and VCC_USIM voltages.
638638

639+
config REGULATOR_MAX20086
640+
tristate "Maxim MAX20086-MAX20089 Camera Power Protectors"
641+
depends on I2C
642+
select REGMAP_I2C
643+
help
644+
This driver controls a Maxim MAX20086-MAX20089 camera power
645+
protectorvia I2C bus. The regulator has 2 or 4 outputs depending on
646+
the device model. This driver is only capable to turn on/off them.
647+
639648
config REGULATOR_MAX77686
640649
tristate "Maxim 77686 regulator"
641650
depends on MFD_MAX77686 || COMPILE_TEST
@@ -1424,4 +1433,3 @@ config REGULATOR_QCOM_LABIBB
14241433
for LCD display panel.
14251434

14261435
endif
1427-

drivers/regulator/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ obj-$(CONFIG_REGULATOR_MAX8952) += max8952.o
7878
obj-$(CONFIG_REGULATOR_MAX8973) += max8973-regulator.o
7979
obj-$(CONFIG_REGULATOR_MAX8997) += max8997-regulator.o
8080
obj-$(CONFIG_REGULATOR_MAX8998) += max8998.o
81+
obj-$(CONFIG_REGULATOR_MAX20086) += max20086-regulator.o
8182
obj-$(CONFIG_REGULATOR_MAX77686) += max77686-regulator.o
8283
obj-$(CONFIG_REGULATOR_MAX77693) += max77693-regulator.o
8384
obj-$(CONFIG_REGULATOR_MAX77802) += max77802-regulator.o
Lines changed: 332 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,332 @@
1+
// SPDX-License-Identifier: GPL-2.0-or-later
2+
//
3+
// max20086-regulator.c - MAX20086-MAX20089 camera power protector driver
4+
//
5+
// Copyright (C) 2022 Laurent Pinchart <laurent.pinchart@idesonboard.com>
6+
// Copyright (C) 2018 Avnet, Inc.
7+
8+
#include <linux/err.h>
9+
#include <linux/gpio.h>
10+
#include <linux/i2c.h>
11+
#include <linux/module.h>
12+
#include <linux/regmap.h>
13+
#include <linux/regulator/driver.h>
14+
#include <linux/regulator/machine.h>
15+
#include <linux/regulator/of_regulator.h>
16+
#include <linux/slab.h>
17+
18+
/* Register Offset */
19+
#define MAX20086_REG_MASK 0x00
20+
#define MAX20086_REG_CONFIG 0x01
21+
#define MAX20086_REG_ID 0x02
22+
#define MAX20086_REG_STAT1 0x03
23+
#define MAX20086_REG_STAT2_L 0x04
24+
#define MAX20086_REG_STAT2_H 0x05
25+
#define MAX20086_REG_ADC1 0x06
26+
#define MAX20086_REG_ADC2 0x07
27+
#define MAX20086_REG_ADC3 0x08
28+
#define MAX20086_REG_ADC4 0x09
29+
30+
/* DEVICE IDs */
31+
#define MAX20086_DEVICE_ID_MAX20086 0x40
32+
#define MAX20086_DEVICE_ID_MAX20087 0x20
33+
#define MAX20086_DEVICE_ID_MAX20088 0x10
34+
#define MAX20086_DEVICE_ID_MAX20089 0x00
35+
#define DEVICE_ID_MASK 0xf0
36+
37+
/* Register bits */
38+
#define MAX20086_EN_MASK 0x0f
39+
#define MAX20086_EN_OUT1 0x01
40+
#define MAX20086_EN_OUT2 0x02
41+
#define MAX20086_EN_OUT3 0x04
42+
#define MAX20086_EN_OUT4 0x08
43+
#define MAX20086_INT_DISABLE_ALL 0x3f
44+
45+
#define MAX20086_MAX_REGULATORS 4
46+
47+
struct max20086_chip_info {
48+
u8 id;
49+
unsigned int num_outputs;
50+
};
51+
52+
struct max20086_regulator {
53+
struct device_node *of_node;
54+
struct regulator_init_data *init_data;
55+
const struct regulator_desc *desc;
56+
struct regulator_dev *rdev;
57+
};
58+
59+
struct max20086 {
60+
struct device *dev;
61+
struct regmap *regmap;
62+
struct gpio_desc *ena_gpiod;
63+
64+
const struct max20086_chip_info *info;
65+
66+
struct max20086_regulator regulators[MAX20086_MAX_REGULATORS];
67+
};
68+
69+
static const struct regulator_ops max20086_buck_ops = {
70+
.enable = regulator_enable_regmap,
71+
.disable = regulator_disable_regmap,
72+
.is_enabled = regulator_is_enabled_regmap,
73+
};
74+
75+
#define MAX20086_REGULATOR_DESC(n) \
76+
{ \
77+
.name = "OUT"#n, \
78+
.supply_name = "in", \
79+
.id = (n) - 1, \
80+
.ops = &max20086_buck_ops, \
81+
.type = REGULATOR_VOLTAGE, \
82+
.owner = THIS_MODULE, \
83+
.enable_reg = MAX20086_REG_CONFIG, \
84+
.enable_mask = 1 << ((n) - 1), \
85+
.enable_val = 1 << ((n) - 1), \
86+
.disable_val = 0, \
87+
}
88+
89+
static const char * const max20086_output_names[] = {
90+
"OUT1",
91+
"OUT2",
92+
"OUT3",
93+
"OUT4",
94+
};
95+
96+
static const struct regulator_desc max20086_regulators[] = {
97+
MAX20086_REGULATOR_DESC(1),
98+
MAX20086_REGULATOR_DESC(2),
99+
MAX20086_REGULATOR_DESC(3),
100+
MAX20086_REGULATOR_DESC(4),
101+
};
102+
103+
static int max20086_regulators_register(struct max20086 *chip)
104+
{
105+
unsigned int i;
106+
107+
for (i = 0; i < chip->info->num_outputs; i++) {
108+
struct max20086_regulator *reg = &chip->regulators[i];
109+
struct regulator_config config = { };
110+
struct regulator_dev *rdev;
111+
112+
config.dev = chip->dev;
113+
config.init_data = reg->init_data;
114+
config.driver_data = chip;
115+
config.of_node = reg->of_node;
116+
config.regmap = chip->regmap;
117+
config.ena_gpiod = chip->ena_gpiod;
118+
119+
rdev = devm_regulator_register(chip->dev, reg->desc, &config);
120+
if (IS_ERR(rdev)) {
121+
dev_err(chip->dev,
122+
"Failed to register regulator output %s\n",
123+
reg->desc->name);
124+
return PTR_ERR(rdev);
125+
}
126+
127+
reg->rdev = rdev;
128+
}
129+
130+
return 0;
131+
}
132+
133+
static int max20086_parse_regulators_dt(struct max20086 *chip, bool *boot_on)
134+
{
135+
struct of_regulator_match matches[MAX20086_MAX_REGULATORS] = { };
136+
struct device_node *node;
137+
unsigned int i;
138+
int ret;
139+
140+
node = of_get_child_by_name(chip->dev->of_node, "regulators");
141+
if (!node) {
142+
dev_err(chip->dev, "regulators node not found\n");
143+
return PTR_ERR(node);
144+
}
145+
146+
for (i = 0; i < chip->info->num_outputs; ++i)
147+
matches[i].name = max20086_output_names[i];
148+
149+
ret = of_regulator_match(chip->dev, node, matches,
150+
chip->info->num_outputs);
151+
of_node_put(node);
152+
if (ret < 0) {
153+
dev_err(chip->dev, "Failed to match regulators\n");
154+
return -EINVAL;
155+
}
156+
157+
*boot_on = false;
158+
159+
for (i = 0; i < chip->info->num_outputs; i++) {
160+
struct max20086_regulator *reg = &chip->regulators[i];
161+
162+
reg->init_data = matches[i].init_data;
163+
reg->of_node = matches[i].of_node;
164+
reg->desc = &max20086_regulators[i];
165+
166+
if (reg->init_data) {
167+
if (reg->init_data->constraints.always_on ||
168+
reg->init_data->constraints.boot_on)
169+
*boot_on = true;
170+
}
171+
}
172+
173+
return 0;
174+
}
175+
176+
static int max20086_detect(struct max20086 *chip)
177+
{
178+
unsigned int data;
179+
int ret;
180+
181+
ret = regmap_read(chip->regmap, MAX20086_REG_ID, &data);
182+
if (ret < 0) {
183+
dev_err(chip->dev, "Failed to read DEVICE_ID reg: %d\n", ret);
184+
return ret;
185+
}
186+
187+
if ((data & DEVICE_ID_MASK) != chip->info->id) {
188+
dev_err(chip->dev, "Invalid device ID 0x%02x\n", data);
189+
return -ENXIO;
190+
}
191+
192+
return 0;
193+
}
194+
195+
static bool max20086_gen_is_writeable_reg(struct device *dev, unsigned int reg)
196+
{
197+
switch (reg) {
198+
case MAX20086_REG_MASK:
199+
case MAX20086_REG_CONFIG:
200+
return true;
201+
default:
202+
return false;
203+
}
204+
}
205+
206+
static const struct regmap_config max20086_regmap_config = {
207+
.reg_bits = 8,
208+
.val_bits = 8,
209+
.writeable_reg = max20086_gen_is_writeable_reg,
210+
.max_register = 0x9,
211+
.cache_type = REGCACHE_NONE,
212+
};
213+
214+
static int max20086_i2c_probe(struct i2c_client *i2c)
215+
{
216+
struct max20086 *chip;
217+
enum gpiod_flags flags;
218+
bool boot_on;
219+
int ret;
220+
221+
chip = devm_kzalloc(&i2c->dev, sizeof(*chip), GFP_KERNEL);
222+
if (!chip)
223+
return -ENOMEM;
224+
225+
chip->dev = &i2c->dev;
226+
chip->info = device_get_match_data(chip->dev);
227+
228+
i2c_set_clientdata(i2c, chip);
229+
230+
chip->regmap = devm_regmap_init_i2c(i2c, &max20086_regmap_config);
231+
if (IS_ERR(chip->regmap)) {
232+
ret = PTR_ERR(chip->regmap);
233+
dev_err(chip->dev, "Failed to allocate register map: %d\n", ret);
234+
return ret;
235+
}
236+
237+
ret = max20086_parse_regulators_dt(chip, &boot_on);
238+
if (ret < 0)
239+
return ret;
240+
241+
ret = max20086_detect(chip);
242+
if (ret < 0)
243+
return ret;
244+
245+
/* Until IRQ support is added, just disable all interrupts. */
246+
ret = regmap_update_bits(chip->regmap, MAX20086_REG_MASK,
247+
MAX20086_INT_DISABLE_ALL,
248+
MAX20086_INT_DISABLE_ALL);
249+
if (ret < 0) {
250+
dev_err(chip->dev, "Failed to disable interrupts: %d\n", ret);
251+
return ret;
252+
}
253+
254+
/*
255+
* Get the enable GPIO. If any of the outputs is marked as being
256+
* enabled at boot, request the GPIO with an initial high state to
257+
* avoid disabling outputs that may have been turned on by the boot
258+
* loader. Otherwise, request it with a low state to enter lower-power
259+
* shutdown.
260+
*/
261+
flags = boot_on ? GPIOD_OUT_HIGH : GPIOD_OUT_LOW;
262+
chip->ena_gpiod = devm_gpiod_get(chip->dev, "enable", flags);
263+
if (IS_ERR(chip->ena_gpiod)) {
264+
ret = PTR_ERR(chip->ena_gpiod);
265+
dev_err(chip->dev, "Failed to get enable GPIO: %d\n", ret);
266+
return ret;
267+
}
268+
269+
ret = max20086_regulators_register(chip);
270+
if (ret < 0) {
271+
dev_err(chip->dev, "Failed to register regulators: %d\n", ret);
272+
return ret;
273+
}
274+
275+
return 0;
276+
}
277+
278+
static const struct i2c_device_id max20086_i2c_id[] = {
279+
{ "max20086" },
280+
{ "max20087" },
281+
{ "max20088" },
282+
{ "max20089" },
283+
{ /* Sentinel */ },
284+
};
285+
286+
MODULE_DEVICE_TABLE(i2c, max20086_i2c_id);
287+
288+
static const struct of_device_id max20086_dt_ids[] = {
289+
{
290+
.compatible = "maxim,max20086",
291+
.data = &(const struct max20086_chip_info) {
292+
.id = MAX20086_DEVICE_ID_MAX20086,
293+
.num_outputs = 4,
294+
}
295+
}, {
296+
.compatible = "maxim,max20087",
297+
.data = &(const struct max20086_chip_info) {
298+
.id = MAX20086_DEVICE_ID_MAX20087,
299+
.num_outputs = 4,
300+
}
301+
}, {
302+
.compatible = "maxim,max20088",
303+
.data = &(const struct max20086_chip_info) {
304+
.id = MAX20086_DEVICE_ID_MAX20088,
305+
.num_outputs = 2,
306+
}
307+
}, {
308+
.compatible = "maxim,max20089",
309+
.data = &(const struct max20086_chip_info) {
310+
.id = MAX20086_DEVICE_ID_MAX20089,
311+
.num_outputs = 2,
312+
}
313+
},
314+
{ /* Sentinel */ },
315+
};
316+
317+
MODULE_DEVICE_TABLE(of, max20086_dt_ids);
318+
319+
static struct i2c_driver max20086_regulator_driver = {
320+
.driver = {
321+
.name = "max20086",
322+
.of_match_table = of_match_ptr(max20086_dt_ids),
323+
},
324+
.probe_new = max20086_i2c_probe,
325+
.id_table = max20086_i2c_id,
326+
};
327+
328+
module_i2c_driver(max20086_regulator_driver);
329+
330+
MODULE_AUTHOR("Watson Chow <watson.chow@avnet.com>");
331+
MODULE_DESCRIPTION("MAX20086-MAX20089 Camera Power Protector Driver");
332+
MODULE_LICENSE("GPL");

0 commit comments

Comments
 (0)