diff options
author | Teodora Baluta <teodora.baluta@intel.com> | 2015-08-20 10:37:31 -0400 |
---|---|---|
committer | Jonathan Cameron <jic23@kernel.org> | 2015-08-31 12:32:05 -0400 |
commit | 077377fc4f74899c58e946e47352216412d0bb3a (patch) | |
tree | a00dcc7a9ff50941275c55017da1a2fae7eae47a | |
parent | b1d125cc6236399258025b0c5646cafa2b45e043 (diff) |
iio: accel: add support for mxc4005 accelerometer
This patch adds support for Memsic MXC4005XC 3-axis accelerometer. The
current implementation is a minimal one as it adds raw readings for the
three axes and setting scale from userspace.
Signed-off-by: Teodora Baluta <teodora.baluta@intel.com>
Signed-off-by: Jonathan Cameron <jic23@kernel.org>
-rw-r--r-- | drivers/iio/accel/Kconfig | 11 | ||||
-rw-r--r-- | drivers/iio/accel/Makefile | 2 | ||||
-rw-r--r-- | drivers/iio/accel/mxc4005.c | 354 |
3 files changed, 367 insertions, 0 deletions
diff --git a/drivers/iio/accel/Kconfig b/drivers/iio/accel/Kconfig index a59047d7657e..69302bed2860 100644 --- a/drivers/iio/accel/Kconfig +++ b/drivers/iio/accel/Kconfig | |||
@@ -137,6 +137,17 @@ config MMA9553 | |||
137 | To compile this driver as a module, choose M here: the module | 137 | To compile this driver as a module, choose M here: the module |
138 | will be called mma9553. | 138 | will be called mma9553. |
139 | 139 | ||
140 | config MXC4005 | ||
141 | tristate "Memsic MXC4005XC 3-Axis Accelerometer Driver" | ||
142 | depends on I2C | ||
143 | select REGMAP_I2C | ||
144 | help | ||
145 | Say yes here to build support for the Memsic MXC4005XC 3-axis | ||
146 | accelerometer. | ||
147 | |||
148 | To compile this driver as a module, choose M. The module will be | ||
149 | called mxc4005. | ||
150 | |||
140 | config STK8312 | 151 | config STK8312 |
141 | tristate "Sensortek STK8312 3-Axis Accelerometer Driver" | 152 | tristate "Sensortek STK8312 3-Axis Accelerometer Driver" |
142 | depends on I2C | 153 | depends on I2C |
diff --git a/drivers/iio/accel/Makefile b/drivers/iio/accel/Makefile index ebd2675b2a02..020dda0ae508 100644 --- a/drivers/iio/accel/Makefile +++ b/drivers/iio/accel/Makefile | |||
@@ -14,6 +14,8 @@ obj-$(CONFIG_MMA9551_CORE) += mma9551_core.o | |||
14 | obj-$(CONFIG_MMA9551) += mma9551.o | 14 | obj-$(CONFIG_MMA9551) += mma9551.o |
15 | obj-$(CONFIG_MMA9553) += mma9553.o | 15 | obj-$(CONFIG_MMA9553) += mma9553.o |
16 | 16 | ||
17 | obj-$(CONFIG_MXC4005) += mxc4005.o | ||
18 | |||
17 | obj-$(CONFIG_STK8312) += stk8312.o | 19 | obj-$(CONFIG_STK8312) += stk8312.o |
18 | obj-$(CONFIG_STK8BA50) += stk8ba50.o | 20 | obj-$(CONFIG_STK8BA50) += stk8ba50.o |
19 | 21 | ||
diff --git a/drivers/iio/accel/mxc4005.c b/drivers/iio/accel/mxc4005.c new file mode 100644 index 000000000000..e15c1bd26bd1 --- /dev/null +++ b/drivers/iio/accel/mxc4005.c | |||
@@ -0,0 +1,354 @@ | |||
1 | /* | ||
2 | * 3-axis accelerometer driver for MXC4005XC Memsic sensor | ||
3 | * | ||
4 | * Copyright (c) 2014, Intel Corporation. | ||
5 | * | ||
6 | * This program is free software; you can redistribute it and/or modify it | ||
7 | * under the terms and conditions of the GNU General Public License, | ||
8 | * version 2, as published by the Free Software Foundation. | ||
9 | * | ||
10 | * This program is distributed in the hope it will be useful, but WITHOUT | ||
11 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
12 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for | ||
13 | * more details. | ||
14 | */ | ||
15 | |||
16 | #include <linux/module.h> | ||
17 | #include <linux/i2c.h> | ||
18 | #include <linux/iio/iio.h> | ||
19 | #include <linux/acpi.h> | ||
20 | #include <linux/regmap.h> | ||
21 | #include <linux/iio/sysfs.h> | ||
22 | |||
23 | #define MXC4005_DRV_NAME "mxc4005" | ||
24 | #define MXC4005_REGMAP_NAME "mxc4005_regmap" | ||
25 | |||
26 | #define MXC4005_REG_XOUT_UPPER 0x03 | ||
27 | #define MXC4005_REG_XOUT_LOWER 0x04 | ||
28 | #define MXC4005_REG_YOUT_UPPER 0x05 | ||
29 | #define MXC4005_REG_YOUT_LOWER 0x06 | ||
30 | #define MXC4005_REG_ZOUT_UPPER 0x07 | ||
31 | #define MXC4005_REG_ZOUT_LOWER 0x08 | ||
32 | |||
33 | #define MXC4005_REG_CONTROL 0x0D | ||
34 | #define MXC4005_REG_CONTROL_MASK_FSR GENMASK(6, 5) | ||
35 | #define MXC4005_CONTROL_FSR_SHIFT 5 | ||
36 | |||
37 | #define MXC4005_REG_DEVICE_ID 0x0E | ||
38 | |||
39 | enum mxc4005_axis { | ||
40 | AXIS_X, | ||
41 | AXIS_Y, | ||
42 | AXIS_Z, | ||
43 | }; | ||
44 | |||
45 | enum mxc4005_range { | ||
46 | MXC4005_RANGE_2G, | ||
47 | MXC4005_RANGE_4G, | ||
48 | MXC4005_RANGE_8G, | ||
49 | }; | ||
50 | |||
51 | struct mxc4005_data { | ||
52 | struct device *dev; | ||
53 | struct mutex mutex; | ||
54 | struct regmap *regmap; | ||
55 | }; | ||
56 | |||
57 | /* | ||
58 | * MXC4005 can operate in the following ranges: | ||
59 | * +/- 2G, 4G, 8G (the default +/-2G) | ||
60 | * | ||
61 | * (2 + 2) * 9.81 / (2^12 - 1) = 0.009582 | ||
62 | * (4 + 4) * 9.81 / (2^12 - 1) = 0.019164 | ||
63 | * (8 + 8) * 9.81 / (2^12 - 1) = 0.038329 | ||
64 | */ | ||
65 | static const struct { | ||
66 | u8 range; | ||
67 | int scale; | ||
68 | } mxc4005_scale_table[] = { | ||
69 | {MXC4005_RANGE_2G, 9582}, | ||
70 | {MXC4005_RANGE_4G, 19164}, | ||
71 | {MXC4005_RANGE_8G, 38329}, | ||
72 | }; | ||
73 | |||
74 | |||
75 | static IIO_CONST_ATTR(in_accel_scale_available, "0.009582 0.019164 0.038329"); | ||
76 | |||
77 | static struct attribute *mxc4005_attributes[] = { | ||
78 | &iio_const_attr_in_accel_scale_available.dev_attr.attr, | ||
79 | NULL, | ||
80 | }; | ||
81 | |||
82 | static const struct attribute_group mxc4005_attrs_group = { | ||
83 | .attrs = mxc4005_attributes, | ||
84 | }; | ||
85 | |||
86 | static bool mxc4005_is_readable_reg(struct device *dev, unsigned int reg) | ||
87 | { | ||
88 | switch (reg) { | ||
89 | case MXC4005_REG_XOUT_UPPER: | ||
90 | case MXC4005_REG_XOUT_LOWER: | ||
91 | case MXC4005_REG_YOUT_UPPER: | ||
92 | case MXC4005_REG_YOUT_LOWER: | ||
93 | case MXC4005_REG_ZOUT_UPPER: | ||
94 | case MXC4005_REG_ZOUT_LOWER: | ||
95 | case MXC4005_REG_DEVICE_ID: | ||
96 | case MXC4005_REG_CONTROL: | ||
97 | return true; | ||
98 | default: | ||
99 | return false; | ||
100 | } | ||
101 | } | ||
102 | |||
103 | static bool mxc4005_is_writeable_reg(struct device *dev, unsigned int reg) | ||
104 | { | ||
105 | switch (reg) { | ||
106 | case MXC4005_REG_CONTROL: | ||
107 | return true; | ||
108 | default: | ||
109 | return false; | ||
110 | } | ||
111 | } | ||
112 | |||
113 | static const struct regmap_config mxc4005_regmap_config = { | ||
114 | .name = MXC4005_REGMAP_NAME, | ||
115 | |||
116 | .reg_bits = 8, | ||
117 | .val_bits = 8, | ||
118 | |||
119 | .max_register = MXC4005_REG_DEVICE_ID, | ||
120 | |||
121 | .readable_reg = mxc4005_is_readable_reg, | ||
122 | .writeable_reg = mxc4005_is_writeable_reg, | ||
123 | }; | ||
124 | |||
125 | static int mxc4005_read_axis(struct mxc4005_data *data, | ||
126 | unsigned int addr) | ||
127 | { | ||
128 | __be16 reg; | ||
129 | int ret; | ||
130 | |||
131 | ret = regmap_bulk_read(data->regmap, addr, (u8 *) ®, sizeof(reg)); | ||
132 | if (ret < 0) { | ||
133 | dev_err(data->dev, "failed to read reg %02x\n", addr); | ||
134 | return ret; | ||
135 | } | ||
136 | |||
137 | return be16_to_cpu(reg); | ||
138 | } | ||
139 | |||
140 | static int mxc4005_read_scale(struct mxc4005_data *data) | ||
141 | { | ||
142 | unsigned int reg; | ||
143 | int ret; | ||
144 | int i; | ||
145 | |||
146 | ret = regmap_read(data->regmap, MXC4005_REG_CONTROL, ®); | ||
147 | if (ret < 0) { | ||
148 | dev_err(data->dev, "failed to read reg_control\n"); | ||
149 | return ret; | ||
150 | } | ||
151 | |||
152 | i = reg >> MXC4005_CONTROL_FSR_SHIFT; | ||
153 | |||
154 | if (i < 0 || i >= ARRAY_SIZE(mxc4005_scale_table)) | ||
155 | return -EINVAL; | ||
156 | |||
157 | return mxc4005_scale_table[i].scale; | ||
158 | } | ||
159 | |||
160 | static int mxc4005_set_scale(struct mxc4005_data *data, int val) | ||
161 | { | ||
162 | unsigned int reg; | ||
163 | int i; | ||
164 | int ret; | ||
165 | |||
166 | for (i = 0; i < ARRAY_SIZE(mxc4005_scale_table); i++) { | ||
167 | if (mxc4005_scale_table[i].scale == val) { | ||
168 | reg = i << MXC4005_CONTROL_FSR_SHIFT; | ||
169 | ret = regmap_update_bits(data->regmap, | ||
170 | MXC4005_REG_CONTROL, | ||
171 | MXC4005_REG_CONTROL_MASK_FSR, | ||
172 | reg); | ||
173 | if (ret < 0) | ||
174 | dev_err(data->dev, | ||
175 | "failed to write reg_control\n"); | ||
176 | return ret; | ||
177 | } | ||
178 | } | ||
179 | |||
180 | return -EINVAL; | ||
181 | } | ||
182 | |||
183 | static int mxc4005_read_raw(struct iio_dev *indio_dev, | ||
184 | struct iio_chan_spec const *chan, | ||
185 | int *val, int *val2, long mask) | ||
186 | { | ||
187 | struct mxc4005_data *data = iio_priv(indio_dev); | ||
188 | int ret; | ||
189 | |||
190 | switch (mask) { | ||
191 | case IIO_CHAN_INFO_RAW: | ||
192 | switch (chan->type) { | ||
193 | case IIO_ACCEL: | ||
194 | if (iio_buffer_enabled(indio_dev)) | ||
195 | return -EBUSY; | ||
196 | |||
197 | ret = mxc4005_read_axis(data, chan->address); | ||
198 | if (ret < 0) | ||
199 | return ret; | ||
200 | *val = sign_extend32(ret >> 4, 11); | ||
201 | return IIO_VAL_INT; | ||
202 | default: | ||
203 | return -EINVAL; | ||
204 | } | ||
205 | case IIO_CHAN_INFO_SCALE: | ||
206 | ret = mxc4005_read_scale(data); | ||
207 | if (ret < 0) | ||
208 | return ret; | ||
209 | |||
210 | *val = 0; | ||
211 | *val2 = ret; | ||
212 | return IIO_VAL_INT_PLUS_MICRO; | ||
213 | default: | ||
214 | return -EINVAL; | ||
215 | } | ||
216 | } | ||
217 | |||
218 | static int mxc4005_write_raw(struct iio_dev *indio_dev, | ||
219 | struct iio_chan_spec const *chan, | ||
220 | int val, int val2, long mask) | ||
221 | { | ||
222 | struct mxc4005_data *data = iio_priv(indio_dev); | ||
223 | |||
224 | switch (mask) { | ||
225 | case IIO_CHAN_INFO_SCALE: | ||
226 | if (val != 0) | ||
227 | return -EINVAL; | ||
228 | |||
229 | return mxc4005_set_scale(data, val2); | ||
230 | default: | ||
231 | return -EINVAL; | ||
232 | } | ||
233 | } | ||
234 | |||
235 | static const struct iio_info mxc4005_info = { | ||
236 | .driver_module = THIS_MODULE, | ||
237 | .read_raw = mxc4005_read_raw, | ||
238 | .write_raw = mxc4005_write_raw, | ||
239 | .attrs = &mxc4005_attrs_group, | ||
240 | }; | ||
241 | |||
242 | #define MXC4005_CHANNEL(_axis, _addr) { \ | ||
243 | .type = IIO_ACCEL, \ | ||
244 | .modified = 1, \ | ||
245 | .channel2 = IIO_MOD_##_axis, \ | ||
246 | .address = _addr, \ | ||
247 | .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ | ||
248 | .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ | ||
249 | } | ||
250 | |||
251 | static const struct iio_chan_spec mxc4005_channels[] = { | ||
252 | MXC4005_CHANNEL(X, MXC4005_REG_XOUT_UPPER), | ||
253 | MXC4005_CHANNEL(Y, MXC4005_REG_YOUT_UPPER), | ||
254 | MXC4005_CHANNEL(Z, MXC4005_REG_ZOUT_UPPER), | ||
255 | }; | ||
256 | |||
257 | static int mxc4005_chip_init(struct mxc4005_data *data) | ||
258 | { | ||
259 | int ret; | ||
260 | unsigned int reg; | ||
261 | |||
262 | ret = regmap_read(data->regmap, MXC4005_REG_DEVICE_ID, ®); | ||
263 | if (ret < 0) { | ||
264 | dev_err(data->dev, "failed to read chip id\n"); | ||
265 | return ret; | ||
266 | } | ||
267 | |||
268 | dev_dbg(data->dev, "MXC4005 chip id %02x\n", reg); | ||
269 | |||
270 | return 0; | ||
271 | } | ||
272 | |||
273 | static int mxc4005_probe(struct i2c_client *client, | ||
274 | const struct i2c_device_id *id) | ||
275 | { | ||
276 | struct mxc4005_data *data; | ||
277 | struct iio_dev *indio_dev; | ||
278 | struct regmap *regmap; | ||
279 | int ret; | ||
280 | |||
281 | indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); | ||
282 | if (!indio_dev) | ||
283 | return -ENOMEM; | ||
284 | |||
285 | regmap = devm_regmap_init_i2c(client, &mxc4005_regmap_config); | ||
286 | if (IS_ERR(regmap)) { | ||
287 | dev_err(&client->dev, "failed to initialize regmap\n"); | ||
288 | return PTR_ERR(regmap); | ||
289 | } | ||
290 | |||
291 | data = iio_priv(indio_dev); | ||
292 | i2c_set_clientdata(client, indio_dev); | ||
293 | data->dev = &client->dev; | ||
294 | data->regmap = regmap; | ||
295 | |||
296 | ret = mxc4005_chip_init(data); | ||
297 | if (ret < 0) { | ||
298 | dev_err(&client->dev, "failed to initialize chip\n"); | ||
299 | return ret; | ||
300 | } | ||
301 | |||
302 | mutex_init(&data->mutex); | ||
303 | |||
304 | indio_dev->dev.parent = &client->dev; | ||
305 | indio_dev->channels = mxc4005_channels; | ||
306 | indio_dev->num_channels = ARRAY_SIZE(mxc4005_channels); | ||
307 | indio_dev->name = MXC4005_DRV_NAME; | ||
308 | indio_dev->modes = INDIO_DIRECT_MODE; | ||
309 | indio_dev->info = &mxc4005_info; | ||
310 | |||
311 | ret = iio_device_register(indio_dev); | ||
312 | if (ret < 0) { | ||
313 | dev_err(&client->dev, | ||
314 | "unable to register iio device %d\n", ret); | ||
315 | return ret; | ||
316 | } | ||
317 | |||
318 | return 0; | ||
319 | } | ||
320 | |||
321 | static int mxc4005_remove(struct i2c_client *client) | ||
322 | { | ||
323 | iio_device_unregister(i2c_get_clientdata(client)); | ||
324 | |||
325 | return 0; | ||
326 | } | ||
327 | |||
328 | static const struct acpi_device_id mxc4005_acpi_match[] = { | ||
329 | {"MXC4005", 0}, | ||
330 | { }, | ||
331 | }; | ||
332 | MODULE_DEVICE_TABLE(acpi, mxc4005_acpi_match); | ||
333 | |||
334 | static const struct i2c_device_id mxc4005_id[] = { | ||
335 | {"mxc4005", 0}, | ||
336 | { }, | ||
337 | }; | ||
338 | MODULE_DEVICE_TABLE(i2c, mxc4005_id); | ||
339 | |||
340 | static struct i2c_driver mxc4005_driver = { | ||
341 | .driver = { | ||
342 | .name = MXC4005_DRV_NAME, | ||
343 | .acpi_match_table = ACPI_PTR(mxc4005_acpi_match), | ||
344 | }, | ||
345 | .probe = mxc4005_probe, | ||
346 | .remove = mxc4005_remove, | ||
347 | .id_table = mxc4005_id, | ||
348 | }; | ||
349 | |||
350 | module_i2c_driver(mxc4005_driver); | ||
351 | |||
352 | MODULE_AUTHOR("Teodora Baluta <teodora.baluta@intel.com>"); | ||
353 | MODULE_LICENSE("GPL v2"); | ||
354 | MODULE_DESCRIPTION("MXC4005 3-axis accelerometer driver"); | ||