diff options
author | Linus Walleij <linus.walleij@linaro.org> | 2013-09-22 15:49:18 -0400 |
---|---|---|
committer | Lee Jones <lee.jones@linaro.org> | 2013-10-23 11:21:12 -0400 |
commit | 60013b94d9530346db963474f7fde8aecabaff25 (patch) | |
tree | b3e78336a3e946b923ce0bf6a2d5e381dac4b3e6 /drivers/mfd | |
parent | ca13ce3701900c5b64c2c477a9cfea396c6e79c3 (diff) |
mfd: Add STw481x driver
This adds a driver for the STw481x PMICs found in the Nomadik
family of platforms. This one uses pure device tree probing.
Print some of the OTP registers on boot and register a regulator
MFD child.
Signed-off-by: Linus Walleij <linus.walleij@linaro.org>
Signed-off-by: Lee Jones <lee.jones@linaro.org>
Diffstat (limited to 'drivers/mfd')
-rw-r--r-- | drivers/mfd/Kconfig | 10 | ||||
-rw-r--r-- | drivers/mfd/Makefile | 1 | ||||
-rw-r--r-- | drivers/mfd/stw481x.c | 250 |
3 files changed, 261 insertions, 0 deletions
diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index 914c3d142f78..d078fe4a82ef 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig | |||
@@ -1151,6 +1151,16 @@ config MFD_WM8994 | |||
1151 | core support for the WM8994, in order to use the actual | 1151 | core support for the WM8994, in order to use the actual |
1152 | functionaltiy of the device other drivers must be enabled. | 1152 | functionaltiy of the device other drivers must be enabled. |
1153 | 1153 | ||
1154 | config MFD_STW481X | ||
1155 | bool "Support for ST Microelectronics STw481x" | ||
1156 | depends on I2C && ARCH_NOMADIK | ||
1157 | select REGMAP_I2C | ||
1158 | select MFD_CORE | ||
1159 | help | ||
1160 | Select this option to enable the STw481x chip driver used | ||
1161 | in various ST Microelectronics and ST-Ericsson embedded | ||
1162 | Nomadik series. | ||
1163 | |||
1154 | endmenu | 1164 | endmenu |
1155 | endif | 1165 | endif |
1156 | 1166 | ||
diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile index 15b905c6553c..656cec90a1e5 100644 --- a/drivers/mfd/Makefile +++ b/drivers/mfd/Makefile | |||
@@ -162,3 +162,4 @@ obj-$(CONFIG_MFD_LM3533) += lm3533-core.o lm3533-ctrlbank.o | |||
162 | obj-$(CONFIG_VEXPRESS_CONFIG) += vexpress-config.o vexpress-sysreg.o | 162 | obj-$(CONFIG_VEXPRESS_CONFIG) += vexpress-config.o vexpress-sysreg.o |
163 | obj-$(CONFIG_MFD_RETU) += retu-mfd.o | 163 | obj-$(CONFIG_MFD_RETU) += retu-mfd.o |
164 | obj-$(CONFIG_MFD_AS3711) += as3711.o | 164 | obj-$(CONFIG_MFD_AS3711) += as3711.o |
165 | obj-$(CONFIG_MFD_STW481X) += stw481x.o | ||
diff --git a/drivers/mfd/stw481x.c b/drivers/mfd/stw481x.c new file mode 100644 index 000000000000..1243d5c6a448 --- /dev/null +++ b/drivers/mfd/stw481x.c | |||
@@ -0,0 +1,250 @@ | |||
1 | /* | ||
2 | * Core driver for STw4810/STw4811 | ||
3 | * | ||
4 | * Copyright (C) 2013 ST-Ericsson SA | ||
5 | * Written on behalf of Linaro for ST-Ericsson | ||
6 | * | ||
7 | * Author: Linus Walleij <linus.walleij@linaro.org> | ||
8 | * | ||
9 | * License terms: GNU General Public License (GPL) version 2 | ||
10 | */ | ||
11 | |||
12 | #include <linux/err.h> | ||
13 | #include <linux/i2c.h> | ||
14 | #include <linux/init.h> | ||
15 | #include <linux/mfd/core.h> | ||
16 | #include <linux/mfd/stw481x.h> | ||
17 | #include <linux/module.h> | ||
18 | #include <linux/regmap.h> | ||
19 | #include <linux/spinlock.h> | ||
20 | #include <linux/slab.h> | ||
21 | |||
22 | /* | ||
23 | * This driver can only access the non-USB portions of STw4811, the register | ||
24 | * range 0x00-0x10 dealing with USB is bound to the two special I2C pins used | ||
25 | * for USB control. | ||
26 | */ | ||
27 | |||
28 | /* Registers inside the power control address space */ | ||
29 | #define STW_PC_VCORE_SEL 0x05U | ||
30 | #define STW_PC_VAUX_SEL 0x06U | ||
31 | #define STW_PC_VPLL_SEL 0x07U | ||
32 | |||
33 | /** | ||
34 | * stw481x_get_pctl_reg() - get a power control register | ||
35 | * @stw481x: handle to the stw481x chip | ||
36 | * @reg: power control register to fetch | ||
37 | * | ||
38 | * The power control registers is a set of one-time-programmable registers | ||
39 | * in its own register space, accessed by writing addess bits to these | ||
40 | * two registers: bits 7,6,5 of PCTL_REG_LO corresponds to the 3 LSBs of | ||
41 | * the address and bits 8,9 of PCTL_REG_HI corresponds to the 2 MSBs of | ||
42 | * the address, forming an address space of 5 bits, i.e. 32 registers | ||
43 | * 0x00 ... 0x1f can be obtained. | ||
44 | */ | ||
45 | static int stw481x_get_pctl_reg(struct stw481x *stw481x, u8 reg) | ||
46 | { | ||
47 | u8 msb = (reg >> 3) & 0x03; | ||
48 | u8 lsb = (reg << 5) & 0xe0; | ||
49 | unsigned int val; | ||
50 | u8 vrfy; | ||
51 | int ret; | ||
52 | |||
53 | ret = regmap_write(stw481x->map, STW_PCTL_REG_HI, msb); | ||
54 | if (ret) | ||
55 | return ret; | ||
56 | ret = regmap_write(stw481x->map, STW_PCTL_REG_LO, lsb); | ||
57 | if (ret) | ||
58 | return ret; | ||
59 | ret = regmap_read(stw481x->map, STW_PCTL_REG_HI, &val); | ||
60 | if (ret) | ||
61 | return ret; | ||
62 | vrfy = (val & 0x03) << 3; | ||
63 | ret = regmap_read(stw481x->map, STW_PCTL_REG_LO, &val); | ||
64 | if (ret) | ||
65 | return ret; | ||
66 | vrfy |= ((val >> 5) & 0x07); | ||
67 | if (vrfy != reg) | ||
68 | return -EIO; | ||
69 | return (val >> 1) & 0x0f; | ||
70 | } | ||
71 | |||
72 | static int stw481x_startup(struct stw481x *stw481x) | ||
73 | { | ||
74 | /* Voltages multiplied by 100 */ | ||
75 | u8 vcore_val[] = { 100, 105, 110, 115, 120, 122, 124, 126, 128, | ||
76 | 130, 132, 134, 136, 138, 140, 145 }; | ||
77 | u8 vpll_val[] = { 105, 120, 130, 180 }; | ||
78 | u8 vaux_val[] = { 15, 18, 25, 28 }; | ||
79 | u8 vcore; | ||
80 | u8 vcore_slp; | ||
81 | u8 vpll; | ||
82 | u8 vaux; | ||
83 | bool vaux_en; | ||
84 | bool it_warn; | ||
85 | int ret; | ||
86 | unsigned int val; | ||
87 | |||
88 | ret = regmap_read(stw481x->map, STW_CONF1, &val); | ||
89 | if (ret) | ||
90 | return ret; | ||
91 | vaux_en = !!(val & STW_CONF1_PDN_VAUX); | ||
92 | it_warn = !!(val & STW_CONF1_IT_WARN); | ||
93 | |||
94 | dev_info(&stw481x->client->dev, "voltages %s\n", | ||
95 | (val & STW_CONF1_V_MONITORING) ? "OK" : "LOW"); | ||
96 | dev_info(&stw481x->client->dev, "MMC level shifter %s\n", | ||
97 | (val & STW_CONF1_MMC_LS_STATUS) ? "high impedance" : "ON"); | ||
98 | dev_info(&stw481x->client->dev, "VMMC: %s\n", | ||
99 | (val & STW_CONF1_PDN_VMMC) ? "ON" : "disabled"); | ||
100 | |||
101 | dev_info(&stw481x->client->dev, "STw481x power control registers:\n"); | ||
102 | |||
103 | ret = stw481x_get_pctl_reg(stw481x, STW_PC_VCORE_SEL); | ||
104 | if (ret < 0) | ||
105 | return ret; | ||
106 | vcore = ret & 0x0f; | ||
107 | |||
108 | ret = stw481x_get_pctl_reg(stw481x, STW_PC_VAUX_SEL); | ||
109 | if (ret < 0) | ||
110 | return ret; | ||
111 | vaux = (ret >> 2) & 3; | ||
112 | vpll = (ret >> 4) & 1; /* Save bit 4 */ | ||
113 | |||
114 | ret = stw481x_get_pctl_reg(stw481x, STW_PC_VPLL_SEL); | ||
115 | if (ret < 0) | ||
116 | return ret; | ||
117 | vpll |= (ret >> 1) & 2; | ||
118 | |||
119 | dev_info(&stw481x->client->dev, "VCORE: %u.%uV %s\n", | ||
120 | vcore_val[vcore] / 100, vcore_val[vcore] % 100, | ||
121 | (ret & 4) ? "ON" : "OFF"); | ||
122 | |||
123 | dev_info(&stw481x->client->dev, "VPLL: %u.%uV %s\n", | ||
124 | vpll_val[vpll] / 100, vpll_val[vpll] % 100, | ||
125 | (ret & 0x10) ? "ON" : "OFF"); | ||
126 | |||
127 | dev_info(&stw481x->client->dev, "VAUX: %u.%uV %s\n", | ||
128 | vaux_val[vaux] / 10, vaux_val[vaux] % 10, | ||
129 | vaux_en ? "ON" : "OFF"); | ||
130 | |||
131 | ret = regmap_read(stw481x->map, STW_CONF2, &val); | ||
132 | if (ret) | ||
133 | return ret; | ||
134 | |||
135 | dev_info(&stw481x->client->dev, "TWARN: %s threshold, %s\n", | ||
136 | it_warn ? "below" : "above", | ||
137 | (val & STW_CONF2_MASK_TWARN) ? | ||
138 | "enabled" : "mask through VDDOK"); | ||
139 | dev_info(&stw481x->client->dev, "VMMC: %s\n", | ||
140 | (val & STW_CONF2_VMMC_EXT) ? "internal" : "external"); | ||
141 | dev_info(&stw481x->client->dev, "IT WAKE UP: %s\n", | ||
142 | (val & STW_CONF2_MASK_IT_WAKE_UP) ? "enabled" : "masked"); | ||
143 | dev_info(&stw481x->client->dev, "GPO1: %s\n", | ||
144 | (val & STW_CONF2_GPO1) ? "low" : "high impedance"); | ||
145 | dev_info(&stw481x->client->dev, "GPO2: %s\n", | ||
146 | (val & STW_CONF2_GPO2) ? "low" : "high impedance"); | ||
147 | |||
148 | ret = regmap_read(stw481x->map, STW_VCORE_SLEEP, &val); | ||
149 | if (ret) | ||
150 | return ret; | ||
151 | vcore_slp = val & 0x0f; | ||
152 | dev_info(&stw481x->client->dev, "VCORE SLEEP: %u.%uV\n", | ||
153 | vcore_val[vcore_slp] / 100, vcore_val[vcore_slp] % 100); | ||
154 | |||
155 | return 0; | ||
156 | } | ||
157 | |||
158 | /* | ||
159 | * MFD cells - we have one cell which is selected operation | ||
160 | * mode, and we always have a GPIO cell. | ||
161 | */ | ||
162 | static struct mfd_cell stw481x_cells[] = { | ||
163 | { | ||
164 | .of_compatible = "st,stw481x-vmmc", | ||
165 | .name = "stw481x-vmmc-regulator", | ||
166 | .id = -1, | ||
167 | }, | ||
168 | }; | ||
169 | |||
170 | const struct regmap_config stw481x_regmap_config = { | ||
171 | .reg_bits = 8, | ||
172 | .val_bits = 8, | ||
173 | }; | ||
174 | |||
175 | static int stw481x_probe(struct i2c_client *client, | ||
176 | const struct i2c_device_id *id) | ||
177 | { | ||
178 | struct stw481x *stw481x; | ||
179 | int ret; | ||
180 | int i; | ||
181 | |||
182 | stw481x = devm_kzalloc(&client->dev, sizeof(*stw481x), GFP_KERNEL); | ||
183 | if (!stw481x) | ||
184 | return -ENOMEM; | ||
185 | |||
186 | i2c_set_clientdata(client, stw481x); | ||
187 | stw481x->client = client; | ||
188 | stw481x->map = devm_regmap_init_i2c(client, &stw481x_regmap_config); | ||
189 | |||
190 | ret = stw481x_startup(stw481x); | ||
191 | if (ret) { | ||
192 | dev_err(&client->dev, "chip initialization failed\n"); | ||
193 | return ret; | ||
194 | } | ||
195 | |||
196 | /* Set up and register the platform devices. */ | ||
197 | for (i = 0; i < ARRAY_SIZE(stw481x_cells); i++) { | ||
198 | /* One state holder for all drivers, this is simple */ | ||
199 | stw481x_cells[i].platform_data = stw481x; | ||
200 | stw481x_cells[i].pdata_size = sizeof(*stw481x); | ||
201 | } | ||
202 | |||
203 | ret = mfd_add_devices(&client->dev, 0, stw481x_cells, | ||
204 | ARRAY_SIZE(stw481x_cells), NULL, 0, NULL); | ||
205 | if (ret) | ||
206 | return ret; | ||
207 | |||
208 | dev_info(&client->dev, "initialized STw481x device\n"); | ||
209 | |||
210 | return ret; | ||
211 | } | ||
212 | |||
213 | static int stw481x_remove(struct i2c_client *client) | ||
214 | { | ||
215 | mfd_remove_devices(&client->dev); | ||
216 | return 0; | ||
217 | } | ||
218 | |||
219 | /* | ||
220 | * This ID table is completely unused, as this is a pure | ||
221 | * device-tree probed driver, but it has to be here due to | ||
222 | * the structure of the I2C core. | ||
223 | */ | ||
224 | static const struct i2c_device_id stw481x_id[] = { | ||
225 | { "stw481x", 0 }, | ||
226 | { }, | ||
227 | }; | ||
228 | |||
229 | static const struct of_device_id stw481x_match[] = { | ||
230 | { .compatible = "st,stw4810", }, | ||
231 | { .compatible = "st,stw4811", }, | ||
232 | { }, | ||
233 | }; | ||
234 | MODULE_DEVICE_TABLE(of, stw481x_match); | ||
235 | |||
236 | static struct i2c_driver stw481x_driver = { | ||
237 | .driver = { | ||
238 | .name = "stw481x", | ||
239 | .of_match_table = stw481x_match, | ||
240 | }, | ||
241 | .probe = stw481x_probe, | ||
242 | .remove = stw481x_remove, | ||
243 | .id_table = stw481x_id, | ||
244 | }; | ||
245 | |||
246 | module_i2c_driver(stw481x_driver); | ||
247 | |||
248 | MODULE_AUTHOR("Linus Walleij"); | ||
249 | MODULE_DESCRIPTION("STw481x PMIC driver"); | ||
250 | MODULE_LICENSE("GPL v2"); | ||