Commit | Line | Data |
---|---|---|
60b050ff LC |
1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* | |
3 | * Maxim MAX77714 Core Driver | |
4 | * | |
5 | * Copyright (C) 2022 Luca Ceresoli | |
62fa5c98 | 6 | * Author: Luca Ceresoli <luca.ceresoli@bootlin.com> |
60b050ff LC |
7 | */ |
8 | ||
9 | #include <linux/i2c.h> | |
10 | #include <linux/interrupt.h> | |
11 | #include <linux/mfd/core.h> | |
12 | #include <linux/mfd/max77714.h> | |
13 | #include <linux/module.h> | |
14 | #include <linux/of.h> | |
15 | #include <linux/regmap.h> | |
16 | ||
17 | static const struct mfd_cell max77714_cells[] = { | |
18 | { .name = "max77714-watchdog" }, | |
19 | { .name = "max77714-rtc" }, | |
20 | }; | |
21 | ||
22 | static const struct regmap_range max77714_readable_ranges[] = { | |
23 | regmap_reg_range(MAX77714_INT_TOP, MAX77714_INT_TOP), | |
24 | regmap_reg_range(MAX77714_INT_TOPM, MAX77714_INT_TOPM), | |
25 | regmap_reg_range(MAX77714_32K_STATUS, MAX77714_32K_CONFIG), | |
26 | regmap_reg_range(MAX77714_CNFG_GLBL2, MAX77714_CNFG2_ONOFF), | |
27 | }; | |
28 | ||
29 | static const struct regmap_range max77714_writable_ranges[] = { | |
30 | regmap_reg_range(MAX77714_INT_TOPM, MAX77714_INT_TOPM), | |
31 | regmap_reg_range(MAX77714_32K_CONFIG, MAX77714_32K_CONFIG), | |
32 | regmap_reg_range(MAX77714_CNFG_GLBL2, MAX77714_CNFG2_ONOFF), | |
33 | }; | |
34 | ||
35 | static const struct regmap_access_table max77714_readable_table = { | |
36 | .yes_ranges = max77714_readable_ranges, | |
37 | .n_yes_ranges = ARRAY_SIZE(max77714_readable_ranges), | |
38 | }; | |
39 | ||
40 | static const struct regmap_access_table max77714_writable_table = { | |
41 | .yes_ranges = max77714_writable_ranges, | |
42 | .n_yes_ranges = ARRAY_SIZE(max77714_writable_ranges), | |
43 | }; | |
44 | ||
45 | static const struct regmap_config max77714_regmap_config = { | |
46 | .reg_bits = 8, | |
47 | .val_bits = 8, | |
48 | .max_register = MAX77714_CNFG2_ONOFF, | |
49 | .rd_table = &max77714_readable_table, | |
50 | .wr_table = &max77714_writable_table, | |
51 | }; | |
52 | ||
53 | static const struct regmap_irq max77714_top_irqs[] = { | |
54 | REGMAP_IRQ_REG(MAX77714_IRQ_TOP_ONOFF, 0, MAX77714_INT_TOP_ONOFF), | |
55 | REGMAP_IRQ_REG(MAX77714_IRQ_TOP_RTC, 0, MAX77714_INT_TOP_RTC), | |
56 | REGMAP_IRQ_REG(MAX77714_IRQ_TOP_GPIO, 0, MAX77714_INT_TOP_GPIO), | |
57 | REGMAP_IRQ_REG(MAX77714_IRQ_TOP_LDO, 0, MAX77714_INT_TOP_LDO), | |
58 | REGMAP_IRQ_REG(MAX77714_IRQ_TOP_SD, 0, MAX77714_INT_TOP_SD), | |
59 | REGMAP_IRQ_REG(MAX77714_IRQ_TOP_GLBL, 0, MAX77714_INT_TOP_GLBL), | |
60 | }; | |
61 | ||
62 | static const struct regmap_irq_chip max77714_irq_chip = { | |
63 | .name = "max77714-pmic", | |
64 | .status_base = MAX77714_INT_TOP, | |
65 | .mask_base = MAX77714_INT_TOPM, | |
66 | .num_regs = 1, | |
67 | .irqs = max77714_top_irqs, | |
68 | .num_irqs = ARRAY_SIZE(max77714_top_irqs), | |
69 | }; | |
70 | ||
71 | /* | |
72 | * MAX77714 initially uses the internal, low precision oscillator. Enable | |
73 | * the external oscillator by setting the XOSC_RETRY bit. If the external | |
74 | * oscillator is not OK (probably not installed) this has no effect. | |
75 | */ | |
76 | static int max77714_setup_xosc(struct device *dev, struct regmap *regmap) | |
77 | { | |
78 | /* Internal Crystal Load Capacitance, indexed by value of 32KLOAD bits */ | |
79 | static const unsigned int load_cap[4] = {0, 10, 12, 22}; /* pF */ | |
80 | unsigned int load_cap_idx; | |
81 | unsigned int status; | |
82 | int err; | |
83 | ||
84 | err = regmap_update_bits(regmap, MAX77714_32K_CONFIG, | |
85 | MAX77714_32K_CONFIG_XOSC_RETRY, | |
86 | MAX77714_32K_CONFIG_XOSC_RETRY); | |
87 | if (err) | |
88 | return dev_err_probe(dev, err, "Failed to configure the external oscillator\n"); | |
89 | ||
90 | err = regmap_read(regmap, MAX77714_32K_STATUS, &status); | |
91 | if (err) | |
92 | return dev_err_probe(dev, err, "Failed to read external oscillator status\n"); | |
93 | ||
94 | load_cap_idx = (status >> MAX77714_32K_STATUS_32KLOAD_SHF) | |
95 | & MAX77714_32K_STATUS_32KLOAD_MSK; | |
96 | ||
97 | dev_info(dev, "Using %s oscillator, %d pF load cap\n", | |
98 | status & MAX77714_32K_STATUS_32KSOURCE ? "internal" : "external", | |
99 | load_cap[load_cap_idx]); | |
100 | ||
101 | return 0; | |
102 | } | |
103 | ||
104 | static int max77714_probe(struct i2c_client *client) | |
105 | { | |
106 | struct device *dev = &client->dev; | |
107 | struct regmap *regmap; | |
108 | struct regmap_irq_chip_data *irq_data; | |
109 | int err; | |
110 | ||
111 | regmap = devm_regmap_init_i2c(client, &max77714_regmap_config); | |
112 | if (IS_ERR(regmap)) | |
113 | return dev_err_probe(dev, PTR_ERR(regmap), | |
114 | "Failed to initialise regmap\n"); | |
115 | ||
116 | err = max77714_setup_xosc(dev, regmap); | |
117 | if (err) | |
118 | return err; | |
119 | ||
120 | err = devm_regmap_add_irq_chip(dev, regmap, client->irq, | |
121 | IRQF_ONESHOT | IRQF_SHARED, 0, | |
122 | &max77714_irq_chip, &irq_data); | |
123 | if (err) | |
124 | return dev_err_probe(dev, err, "Failed to add PMIC IRQ chip\n"); | |
125 | ||
126 | err = devm_mfd_add_devices(dev, PLATFORM_DEVID_NONE, | |
127 | max77714_cells, ARRAY_SIZE(max77714_cells), | |
128 | NULL, 0, NULL); | |
129 | if (err) | |
130 | return dev_err_probe(dev, err, "Failed to register child devices\n"); | |
131 | ||
132 | return 0; | |
133 | } | |
134 | ||
135 | static const struct of_device_id max77714_dt_match[] = { | |
136 | { .compatible = "maxim,max77714" }, | |
137 | {}, | |
138 | }; | |
139 | MODULE_DEVICE_TABLE(of, max77714_dt_match); | |
140 | ||
141 | static struct i2c_driver max77714_driver = { | |
142 | .driver = { | |
143 | .name = "max77714", | |
144 | .of_match_table = max77714_dt_match, | |
145 | }, | |
9816d859 | 146 | .probe = max77714_probe, |
60b050ff LC |
147 | }; |
148 | module_i2c_driver(max77714_driver); | |
149 | ||
150 | MODULE_DESCRIPTION("Maxim MAX77714 MFD core driver"); | |
62fa5c98 | 151 | MODULE_AUTHOR("Luca Ceresoli <luca.ceresoli@bootlin.com>"); |
60b050ff | 152 | MODULE_LICENSE("GPL"); |