1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Freescale MMA7660FC 3-Axis Accelerometer |
4 | * |
5 | * Copyright (c) 2016, Intel Corporation. |
6 | * |
7 | * IIO driver for Freescale MMA7660FC; 7-bit I2C address: 0x4c. |
8 | */ |
9 | |
10 | #include <linux/i2c.h> |
11 | #include <linux/mod_devicetable.h> |
12 | #include <linux/module.h> |
13 | #include <linux/iio/iio.h> |
14 | #include <linux/iio/sysfs.h> |
15 | |
16 | #define MMA7660_DRIVER_NAME "mma7660" |
17 | |
18 | #define MMA7660_REG_XOUT 0x00 |
19 | #define MMA7660_REG_YOUT 0x01 |
20 | #define MMA7660_REG_ZOUT 0x02 |
21 | #define MMA7660_REG_OUT_BIT_ALERT BIT(6) |
22 | |
23 | #define MMA7660_REG_MODE 0x07 |
24 | #define MMA7660_REG_MODE_BIT_MODE BIT(0) |
25 | #define MMA7660_REG_MODE_BIT_TON BIT(2) |
26 | |
27 | #define MMA7660_I2C_READ_RETRIES 5 |
28 | |
29 | /* |
30 | * The accelerometer has one measurement range: |
31 | * |
32 | * -1.5g - +1.5g (6-bit, signed) |
33 | * |
34 | * scale = (1.5 + 1.5) * 9.81 / (2^6 - 1) = 0.467142857 |
35 | */ |
36 | |
37 | #define MMA7660_SCALE_AVAIL "0.467142857" |
38 | |
39 | static const int mma7660_nscale = 467142857; |
40 | |
41 | #define MMA7660_CHANNEL(reg, axis) { \ |
42 | .type = IIO_ACCEL, \ |
43 | .address = reg, \ |
44 | .modified = 1, \ |
45 | .channel2 = IIO_MOD_##axis, \ |
46 | .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ |
47 | .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ |
48 | } |
49 | |
50 | static const struct iio_chan_spec mma7660_channels[] = { |
51 | MMA7660_CHANNEL(MMA7660_REG_XOUT, X), |
52 | MMA7660_CHANNEL(MMA7660_REG_YOUT, Y), |
53 | MMA7660_CHANNEL(MMA7660_REG_ZOUT, Z), |
54 | }; |
55 | |
56 | enum mma7660_mode { |
57 | MMA7660_MODE_STANDBY, |
58 | MMA7660_MODE_ACTIVE |
59 | }; |
60 | |
61 | struct mma7660_data { |
62 | struct i2c_client *client; |
63 | struct mutex lock; |
64 | enum mma7660_mode mode; |
65 | }; |
66 | |
67 | static IIO_CONST_ATTR(in_accel_scale_available, MMA7660_SCALE_AVAIL); |
68 | |
69 | static struct attribute *mma7660_attributes[] = { |
70 | &iio_const_attr_in_accel_scale_available.dev_attr.attr, |
71 | NULL, |
72 | }; |
73 | |
74 | static const struct attribute_group mma7660_attribute_group = { |
75 | .attrs = mma7660_attributes |
76 | }; |
77 | |
78 | static int mma7660_set_mode(struct mma7660_data *data, |
79 | enum mma7660_mode mode) |
80 | { |
81 | int ret; |
82 | struct i2c_client *client = data->client; |
83 | |
84 | if (mode == data->mode) |
85 | return 0; |
86 | |
87 | ret = i2c_smbus_read_byte_data(client, MMA7660_REG_MODE); |
88 | if (ret < 0) { |
89 | dev_err(&client->dev, "failed to read sensor mode\n" ); |
90 | return ret; |
91 | } |
92 | |
93 | if (mode == MMA7660_MODE_ACTIVE) { |
94 | ret &= ~MMA7660_REG_MODE_BIT_TON; |
95 | ret |= MMA7660_REG_MODE_BIT_MODE; |
96 | } else { |
97 | ret &= ~MMA7660_REG_MODE_BIT_TON; |
98 | ret &= ~MMA7660_REG_MODE_BIT_MODE; |
99 | } |
100 | |
101 | ret = i2c_smbus_write_byte_data(client, MMA7660_REG_MODE, value: ret); |
102 | if (ret < 0) { |
103 | dev_err(&client->dev, "failed to change sensor mode\n" ); |
104 | return ret; |
105 | } |
106 | |
107 | data->mode = mode; |
108 | |
109 | return ret; |
110 | } |
111 | |
112 | static int mma7660_read_accel(struct mma7660_data *data, u8 address) |
113 | { |
114 | int ret, retries = MMA7660_I2C_READ_RETRIES; |
115 | struct i2c_client *client = data->client; |
116 | |
117 | /* |
118 | * Read data. If the Alert bit is set, the register was read at |
119 | * the same time as the device was attempting to update the content. |
120 | * The solution is to read the register again. Do this only |
121 | * MMA7660_I2C_READ_RETRIES times to avoid spending too much time |
122 | * in the kernel. |
123 | */ |
124 | do { |
125 | ret = i2c_smbus_read_byte_data(client, command: address); |
126 | if (ret < 0) { |
127 | dev_err(&client->dev, "register read failed\n" ); |
128 | return ret; |
129 | } |
130 | } while (retries-- > 0 && ret & MMA7660_REG_OUT_BIT_ALERT); |
131 | |
132 | if (ret & MMA7660_REG_OUT_BIT_ALERT) { |
133 | dev_err(&client->dev, "all register read retries failed\n" ); |
134 | return -ETIMEDOUT; |
135 | } |
136 | |
137 | return ret; |
138 | } |
139 | |
140 | static int mma7660_read_raw(struct iio_dev *indio_dev, |
141 | struct iio_chan_spec const *chan, |
142 | int *val, int *val2, long mask) |
143 | { |
144 | struct mma7660_data *data = iio_priv(indio_dev); |
145 | int ret; |
146 | |
147 | switch (mask) { |
148 | case IIO_CHAN_INFO_RAW: |
149 | mutex_lock(&data->lock); |
150 | ret = mma7660_read_accel(data, address: chan->address); |
151 | mutex_unlock(lock: &data->lock); |
152 | if (ret < 0) |
153 | return ret; |
154 | *val = sign_extend32(value: ret, index: 5); |
155 | return IIO_VAL_INT; |
156 | case IIO_CHAN_INFO_SCALE: |
157 | *val = 0; |
158 | *val2 = mma7660_nscale; |
159 | return IIO_VAL_INT_PLUS_NANO; |
160 | default: |
161 | return -EINVAL; |
162 | } |
163 | |
164 | return -EINVAL; |
165 | } |
166 | |
167 | static const struct iio_info mma7660_info = { |
168 | .read_raw = mma7660_read_raw, |
169 | .attrs = &mma7660_attribute_group, |
170 | }; |
171 | |
172 | static int mma7660_probe(struct i2c_client *client) |
173 | { |
174 | int ret; |
175 | struct iio_dev *indio_dev; |
176 | struct mma7660_data *data; |
177 | |
178 | indio_dev = devm_iio_device_alloc(parent: &client->dev, sizeof_priv: sizeof(*data)); |
179 | if (!indio_dev) { |
180 | dev_err(&client->dev, "iio allocation failed!\n" ); |
181 | return -ENOMEM; |
182 | } |
183 | |
184 | data = iio_priv(indio_dev); |
185 | data->client = client; |
186 | i2c_set_clientdata(client, data: indio_dev); |
187 | mutex_init(&data->lock); |
188 | data->mode = MMA7660_MODE_STANDBY; |
189 | |
190 | indio_dev->info = &mma7660_info; |
191 | indio_dev->name = MMA7660_DRIVER_NAME; |
192 | indio_dev->modes = INDIO_DIRECT_MODE; |
193 | indio_dev->channels = mma7660_channels; |
194 | indio_dev->num_channels = ARRAY_SIZE(mma7660_channels); |
195 | |
196 | ret = mma7660_set_mode(data, mode: MMA7660_MODE_ACTIVE); |
197 | if (ret < 0) |
198 | return ret; |
199 | |
200 | ret = iio_device_register(indio_dev); |
201 | if (ret < 0) { |
202 | dev_err(&client->dev, "device_register failed\n" ); |
203 | mma7660_set_mode(data, mode: MMA7660_MODE_STANDBY); |
204 | } |
205 | |
206 | return ret; |
207 | } |
208 | |
209 | static void mma7660_remove(struct i2c_client *client) |
210 | { |
211 | struct iio_dev *indio_dev = i2c_get_clientdata(client); |
212 | int ret; |
213 | |
214 | iio_device_unregister(indio_dev); |
215 | |
216 | ret = mma7660_set_mode(data: iio_priv(indio_dev), mode: MMA7660_MODE_STANDBY); |
217 | if (ret) |
218 | dev_warn(&client->dev, "Failed to put device in stand-by mode (%pe), ignoring\n" , |
219 | ERR_PTR(ret)); |
220 | } |
221 | |
222 | static int mma7660_suspend(struct device *dev) |
223 | { |
224 | struct mma7660_data *data; |
225 | |
226 | data = iio_priv(indio_dev: i2c_get_clientdata(to_i2c_client(dev))); |
227 | |
228 | return mma7660_set_mode(data, mode: MMA7660_MODE_STANDBY); |
229 | } |
230 | |
231 | static int mma7660_resume(struct device *dev) |
232 | { |
233 | struct mma7660_data *data; |
234 | |
235 | data = iio_priv(indio_dev: i2c_get_clientdata(to_i2c_client(dev))); |
236 | |
237 | return mma7660_set_mode(data, mode: MMA7660_MODE_ACTIVE); |
238 | } |
239 | |
240 | static DEFINE_SIMPLE_DEV_PM_OPS(mma7660_pm_ops, mma7660_suspend, |
241 | mma7660_resume); |
242 | |
243 | static const struct i2c_device_id mma7660_i2c_id[] = { |
244 | {"mma7660" , 0}, |
245 | {} |
246 | }; |
247 | MODULE_DEVICE_TABLE(i2c, mma7660_i2c_id); |
248 | |
249 | static const struct of_device_id mma7660_of_match[] = { |
250 | { .compatible = "fsl,mma7660" }, |
251 | { } |
252 | }; |
253 | MODULE_DEVICE_TABLE(of, mma7660_of_match); |
254 | |
255 | static const struct acpi_device_id mma7660_acpi_id[] = { |
256 | {"MMA7660" , 0}, |
257 | {} |
258 | }; |
259 | |
260 | MODULE_DEVICE_TABLE(acpi, mma7660_acpi_id); |
261 | |
262 | static struct i2c_driver mma7660_driver = { |
263 | .driver = { |
264 | .name = "mma7660" , |
265 | .pm = pm_sleep_ptr(&mma7660_pm_ops), |
266 | .of_match_table = mma7660_of_match, |
267 | .acpi_match_table = mma7660_acpi_id, |
268 | }, |
269 | .probe = mma7660_probe, |
270 | .remove = mma7660_remove, |
271 | .id_table = mma7660_i2c_id, |
272 | }; |
273 | |
274 | module_i2c_driver(mma7660_driver); |
275 | |
276 | MODULE_AUTHOR("Constantin Musca <constantin.musca@intel.com>" ); |
277 | MODULE_DESCRIPTION("Freescale MMA7660FC 3-Axis Accelerometer driver" ); |
278 | MODULE_LICENSE("GPL v2" ); |
279 | |