1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * MXC6255 - MEMSIC orientation sensing accelerometer |
4 | * |
5 | * Copyright (c) 2015, Intel Corporation. |
6 | * |
7 | * IIO driver for MXC6255 (7-bit I2C slave address 0x15). |
8 | */ |
9 | |
10 | #include <linux/module.h> |
11 | #include <linux/i2c.h> |
12 | #include <linux/init.h> |
13 | #include <linux/iio/iio.h> |
14 | #include <linux/delay.h> |
15 | #include <linux/acpi.h> |
16 | #include <linux/regmap.h> |
17 | #include <linux/iio/sysfs.h> |
18 | |
19 | #define MXC6255_DRV_NAME "mxc6255" |
20 | #define MXC6255_REGMAP_NAME "mxc6255_regmap" |
21 | |
22 | #define MXC6255_REG_XOUT 0x00 |
23 | #define MXC6255_REG_YOUT 0x01 |
24 | #define MXC6255_REG_CHIP_ID 0x08 |
25 | |
26 | #define MXC6255_CHIP_ID 0x05 |
27 | |
28 | /* |
29 | * MXC6255 has only one measurement range: +/- 2G. |
30 | * The acceleration output is an 8-bit value. |
31 | * |
32 | * Scale is calculated as follows: |
33 | * (2 + 2) * 9.80665 / (2^8 - 1) = 0.153829 |
34 | * |
35 | * Scale value for +/- 2G measurement range |
36 | */ |
37 | #define MXC6255_SCALE 153829 |
38 | |
39 | enum mxc6255_axis { |
40 | AXIS_X, |
41 | AXIS_Y, |
42 | }; |
43 | |
44 | struct mxc6255_data { |
45 | struct i2c_client *client; |
46 | struct regmap *regmap; |
47 | }; |
48 | |
49 | static int mxc6255_read_raw(struct iio_dev *indio_dev, |
50 | struct iio_chan_spec const *chan, |
51 | int *val, int *val2, long mask) |
52 | { |
53 | struct mxc6255_data *data = iio_priv(indio_dev); |
54 | unsigned int reg; |
55 | int ret; |
56 | |
57 | switch (mask) { |
58 | case IIO_CHAN_INFO_RAW: |
59 | ret = regmap_read(map: data->regmap, reg: chan->address, val: ®); |
60 | if (ret < 0) { |
61 | dev_err(&data->client->dev, |
62 | "Error reading reg %lu\n" , chan->address); |
63 | return ret; |
64 | } |
65 | |
66 | *val = sign_extend32(value: reg, index: 7); |
67 | return IIO_VAL_INT; |
68 | case IIO_CHAN_INFO_SCALE: |
69 | *val = 0; |
70 | *val2 = MXC6255_SCALE; |
71 | return IIO_VAL_INT_PLUS_MICRO; |
72 | default: |
73 | return -EINVAL; |
74 | } |
75 | } |
76 | |
77 | static const struct iio_info mxc6255_info = { |
78 | .read_raw = mxc6255_read_raw, |
79 | }; |
80 | |
81 | #define MXC6255_CHANNEL(_axis, reg) { \ |
82 | .type = IIO_ACCEL, \ |
83 | .modified = 1, \ |
84 | .channel2 = IIO_MOD_##_axis, \ |
85 | .address = reg, \ |
86 | .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ |
87 | .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ |
88 | } |
89 | |
90 | static const struct iio_chan_spec mxc6255_channels[] = { |
91 | MXC6255_CHANNEL(X, MXC6255_REG_XOUT), |
92 | MXC6255_CHANNEL(Y, MXC6255_REG_YOUT), |
93 | }; |
94 | |
95 | static bool mxc6255_is_readable_reg(struct device *dev, unsigned int reg) |
96 | { |
97 | switch (reg) { |
98 | case MXC6255_REG_XOUT: |
99 | case MXC6255_REG_YOUT: |
100 | case MXC6255_REG_CHIP_ID: |
101 | return true; |
102 | default: |
103 | return false; |
104 | } |
105 | } |
106 | |
107 | static const struct regmap_config mxc6255_regmap_config = { |
108 | .name = MXC6255_REGMAP_NAME, |
109 | |
110 | .reg_bits = 8, |
111 | .val_bits = 8, |
112 | |
113 | .readable_reg = mxc6255_is_readable_reg, |
114 | }; |
115 | |
116 | static int mxc6255_probe(struct i2c_client *client) |
117 | { |
118 | struct mxc6255_data *data; |
119 | struct iio_dev *indio_dev; |
120 | struct regmap *regmap; |
121 | unsigned int chip_id; |
122 | int ret; |
123 | |
124 | indio_dev = devm_iio_device_alloc(parent: &client->dev, sizeof_priv: sizeof(*data)); |
125 | if (!indio_dev) |
126 | return -ENOMEM; |
127 | |
128 | regmap = devm_regmap_init_i2c(client, &mxc6255_regmap_config); |
129 | if (IS_ERR(ptr: regmap)) { |
130 | dev_err(&client->dev, "Error initializing regmap\n" ); |
131 | return PTR_ERR(ptr: regmap); |
132 | } |
133 | |
134 | data = iio_priv(indio_dev); |
135 | i2c_set_clientdata(client, data: indio_dev); |
136 | data->client = client; |
137 | data->regmap = regmap; |
138 | |
139 | indio_dev->name = MXC6255_DRV_NAME; |
140 | indio_dev->channels = mxc6255_channels; |
141 | indio_dev->num_channels = ARRAY_SIZE(mxc6255_channels); |
142 | indio_dev->modes = INDIO_DIRECT_MODE; |
143 | indio_dev->info = &mxc6255_info; |
144 | |
145 | ret = regmap_read(map: data->regmap, MXC6255_REG_CHIP_ID, val: &chip_id); |
146 | if (ret < 0) { |
147 | dev_err(&client->dev, "Error reading chip id %d\n" , ret); |
148 | return ret; |
149 | } |
150 | |
151 | if ((chip_id & 0x1f) != MXC6255_CHIP_ID) { |
152 | dev_err(&client->dev, "Invalid chip id %x\n" , chip_id); |
153 | return -ENODEV; |
154 | } |
155 | |
156 | dev_dbg(&client->dev, "Chip id %x\n" , chip_id); |
157 | |
158 | ret = devm_iio_device_register(&client->dev, indio_dev); |
159 | if (ret < 0) { |
160 | dev_err(&client->dev, "Could not register IIO device\n" ); |
161 | return ret; |
162 | } |
163 | |
164 | return 0; |
165 | } |
166 | |
167 | static const struct acpi_device_id mxc6255_acpi_match[] = { |
168 | {"MXC6225" , 0}, |
169 | {"MXC6255" , 0}, |
170 | { } |
171 | }; |
172 | MODULE_DEVICE_TABLE(acpi, mxc6255_acpi_match); |
173 | |
174 | static const struct i2c_device_id mxc6255_id[] = { |
175 | {"mxc6225" , 0}, |
176 | {"mxc6255" , 0}, |
177 | { } |
178 | }; |
179 | MODULE_DEVICE_TABLE(i2c, mxc6255_id); |
180 | |
181 | static struct i2c_driver mxc6255_driver = { |
182 | .driver = { |
183 | .name = MXC6255_DRV_NAME, |
184 | .acpi_match_table = ACPI_PTR(mxc6255_acpi_match), |
185 | }, |
186 | .probe = mxc6255_probe, |
187 | .id_table = mxc6255_id, |
188 | }; |
189 | |
190 | module_i2c_driver(mxc6255_driver); |
191 | |
192 | MODULE_AUTHOR("Teodora Baluta <teodora.baluta@intel.com>" ); |
193 | MODULE_DESCRIPTION("MEMSIC MXC6255 orientation sensing accelerometer driver" ); |
194 | MODULE_LICENSE("GPL v2" ); |
195 | |