Merge tag 'please-pull-misc-3.12' of git://git.kernel.org/pub/scm/linux/kernel/git...
[cascardo/linux.git] / drivers / iio / common / hid-sensors / hid-sensor-attributes.c
1 /*
2  * HID Sensors Driver
3  * Copyright (c) 2012, Intel Corporation.
4  *
5  * This program is free software; you can redistribute it and/or modify it
6  * under the terms and conditions of the GNU General Public License,
7  * version 2, as published by the Free Software Foundation.
8  *
9  * This program is distributed in the hope it will be useful, but WITHOUT
10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
12  * more details.
13  *
14  * You should have received a copy of the GNU General Public License along with
15  * this program; if not, write to the Free Software Foundation, Inc.,
16  * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA.
17  *
18  */
19 #include <linux/device.h>
20 #include <linux/platform_device.h>
21 #include <linux/module.h>
22 #include <linux/interrupt.h>
23 #include <linux/irq.h>
24 #include <linux/slab.h>
25 #include <linux/hid-sensor-hub.h>
26 #include <linux/iio/iio.h>
27 #include <linux/iio/sysfs.h>
28
29 static int pow_10(unsigned power)
30 {
31         int i;
32         int ret = 1;
33         for (i = 0; i < power; ++i)
34                 ret = ret * 10;
35
36         return ret;
37 }
38
39 static void simple_div(int dividend, int divisor, int *whole,
40                                 int *micro_frac)
41 {
42         int rem;
43         int exp = 0;
44
45         *micro_frac = 0;
46         if (divisor == 0) {
47                 *whole = 0;
48                 return;
49         }
50         *whole = dividend/divisor;
51         rem = dividend % divisor;
52         if (rem) {
53                 while (rem <= divisor) {
54                         rem *= 10;
55                         exp++;
56                 }
57                 *micro_frac = (rem / divisor) * pow_10(6-exp);
58         }
59 }
60
61 static void split_micro_fraction(unsigned int no, int exp, int *val1, int *val2)
62 {
63         *val1 = no/pow_10(exp);
64         *val2 = no%pow_10(exp) * pow_10(6-exp);
65 }
66
67 /*
68 VTF format uses exponent and variable size format.
69 For example if the size is 2 bytes
70 0x0067 with VTF16E14 format -> +1.03
71 To convert just change to 0x67 to decimal and use two decimal as E14 stands
72 for 10^-2.
73 Negative numbers are 2's complement
74 */
75 static void convert_from_vtf_format(u32 value, int size, int exp,
76                                         int *val1, int *val2)
77 {
78         int sign = 1;
79
80         if (value & BIT(size*8 - 1)) {
81                 value =  ((1LL << (size * 8)) - value);
82                 sign = -1;
83         }
84         exp = hid_sensor_convert_exponent(exp);
85         if (exp >= 0) {
86                 *val1 = sign * value * pow_10(exp);
87                 *val2 = 0;
88         } else {
89                 split_micro_fraction(value, -exp, val1, val2);
90                 if (*val1)
91                         *val1 = sign * (*val1);
92                 else
93                         *val2 = sign * (*val2);
94         }
95 }
96
97 static u32 convert_to_vtf_format(int size, int exp, int val1, int val2)
98 {
99         u32 value;
100         int sign = 1;
101
102         if (val1 < 0 || val2 < 0)
103                 sign = -1;
104         exp = hid_sensor_convert_exponent(exp);
105         if (exp < 0) {
106                 value = abs(val1) * pow_10(-exp);
107                 value += abs(val2) / pow_10(6+exp);
108         } else
109                 value = abs(val1) / pow_10(exp);
110         if (sign < 0)
111                 value =  ((1LL << (size * 8)) - value);
112
113         return value;
114 }
115
116 int hid_sensor_read_samp_freq_value(struct hid_sensor_common *st,
117                                 int *val1, int *val2)
118 {
119         s32 value;
120         int ret;
121
122         ret = sensor_hub_get_feature(st->hsdev,
123                 st->poll.report_id,
124                 st->poll.index, &value);
125         if (ret < 0 || value < 0) {
126                 *val1 = *val2 = 0;
127                 return -EINVAL;
128         } else {
129                 if (st->poll.units == HID_USAGE_SENSOR_UNITS_MILLISECOND)
130                         simple_div(1000, value, val1, val2);
131                 else if (st->poll.units == HID_USAGE_SENSOR_UNITS_SECOND)
132                         simple_div(1, value, val1, val2);
133                 else {
134                         *val1 = *val2 = 0;
135                         return -EINVAL;
136                 }
137         }
138
139         return IIO_VAL_INT_PLUS_MICRO;
140 }
141 EXPORT_SYMBOL(hid_sensor_read_samp_freq_value);
142
143 int hid_sensor_write_samp_freq_value(struct hid_sensor_common *st,
144                                 int val1, int val2)
145 {
146         s32 value;
147         int ret;
148
149         if (val1 < 0 || val2 < 0)
150                 ret = -EINVAL;
151
152         value = val1 * pow_10(6) + val2;
153         if (value) {
154                 if (st->poll.units == HID_USAGE_SENSOR_UNITS_MILLISECOND)
155                         value = pow_10(9)/value;
156                 else if (st->poll.units == HID_USAGE_SENSOR_UNITS_SECOND)
157                         value = pow_10(6)/value;
158                 else
159                         value = 0;
160         }
161         ret = sensor_hub_set_feature(st->hsdev,
162                 st->poll.report_id,
163                 st->poll.index, value);
164         if (ret < 0 || value < 0)
165                 ret = -EINVAL;
166
167         return ret;
168 }
169 EXPORT_SYMBOL(hid_sensor_write_samp_freq_value);
170
171 int hid_sensor_read_raw_hyst_value(struct hid_sensor_common *st,
172                                 int *val1, int *val2)
173 {
174         s32 value;
175         int ret;
176
177         ret = sensor_hub_get_feature(st->hsdev,
178                 st->sensitivity.report_id,
179                 st->sensitivity.index, &value);
180         if (ret < 0 || value < 0) {
181                 *val1 = *val2 = 0;
182                 return -EINVAL;
183         } else {
184                 convert_from_vtf_format(value, st->sensitivity.size,
185                                         st->sensitivity.unit_expo,
186                                         val1, val2);
187         }
188
189         return IIO_VAL_INT_PLUS_MICRO;
190 }
191 EXPORT_SYMBOL(hid_sensor_read_raw_hyst_value);
192
193 int hid_sensor_write_raw_hyst_value(struct hid_sensor_common *st,
194                                         int val1, int val2)
195 {
196         s32 value;
197         int ret;
198
199         value = convert_to_vtf_format(st->sensitivity.size,
200                                 st->sensitivity.unit_expo,
201                                 val1, val2);
202         ret = sensor_hub_set_feature(st->hsdev,
203                 st->sensitivity.report_id,
204                 st->sensitivity.index, value);
205         if (ret < 0 || value < 0)
206                 ret = -EINVAL;
207
208         return ret;
209 }
210 EXPORT_SYMBOL(hid_sensor_write_raw_hyst_value);
211
212 int hid_sensor_parse_common_attributes(struct hid_sensor_hub_device *hsdev,
213                                         u32 usage_id,
214                                         struct hid_sensor_common *st)
215 {
216
217         sensor_hub_input_get_attribute_info(hsdev,
218                                         HID_FEATURE_REPORT, usage_id,
219                                         HID_USAGE_SENSOR_PROP_REPORT_INTERVAL,
220                                         &st->poll);
221
222         sensor_hub_input_get_attribute_info(hsdev,
223                                         HID_FEATURE_REPORT, usage_id,
224                                         HID_USAGE_SENSOR_PROP_REPORT_STATE,
225                                         &st->report_state);
226
227         sensor_hub_input_get_attribute_info(hsdev,
228                                         HID_FEATURE_REPORT, usage_id,
229                                         HID_USAGE_SENSOR_PROY_POWER_STATE,
230                                         &st->power_state);
231
232         sensor_hub_input_get_attribute_info(hsdev,
233                         HID_FEATURE_REPORT, usage_id,
234                         HID_USAGE_SENSOR_PROP_SENSITIVITY_ABS,
235                          &st->sensitivity);
236
237         hid_dbg(hsdev->hdev, "common attributes: %x:%x, %x:%x, %x:%x %x:%x\n",
238                         st->poll.index, st->poll.report_id,
239                         st->report_state.index, st->report_state.report_id,
240                         st->power_state.index, st->power_state.report_id,
241                         st->sensitivity.index, st->sensitivity.report_id);
242
243         return 0;
244 }
245 EXPORT_SYMBOL(hid_sensor_parse_common_attributes);
246
247 MODULE_AUTHOR("Srinivas Pandruvada <srinivas.pandruvada@intel.com>");
248 MODULE_DESCRIPTION("HID Sensor common attribute processing");
249 MODULE_LICENSE("GPL");