aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAlexander Holler <holler@ahsoftware.de>2012-12-15 07:45:00 -0500
committerJonathan Cameron <jic23@kernel.org>2013-01-06 06:46:10 -0500
commit62e00cb9a39a889c39a4241645e860284a975e6d (patch)
tree2b52804e149a33cefcbb4b7d7db2b52a842a4a4e
parent2974cdf293e1cb00860522624252aeaba502c8bf (diff)
rtc: add rtc-driver for HID sensors of type time
This driver makes the time from HID sensors (hubs) which are offering such available like any other RTC does. It is necessary that all values like year, month etc, are send as 8bit values (1 byte each) and all of them in 1 report. Also the spec HUTRR39b doesn't define the range of the year field, we tread it as 0 - 99 because that's what most RTCs I know about are offering. Currently the time can only be read. Setting the time must be done through sending a report (or a feature). The spec currently doesn't define how and I'm not sure if I just should define something by myself. Signed-off-by: Alexander Holler <holler@ahsoftware.de> Signed-off-by: Jonathan Cameron <jic23@kernel.org>
-rw-r--r--drivers/rtc/Kconfig16
-rw-r--r--drivers/rtc/Makefile1
-rw-r--r--drivers/rtc/rtc-hid-sensor-time.c291
3 files changed, 308 insertions, 0 deletions
diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig
index d0cea02b5dfc..eed335c16cb0 100644
--- a/drivers/rtc/Kconfig
+++ b/drivers/rtc/Kconfig
@@ -1165,4 +1165,20 @@ config RTC_DRV_SNVS
1165 This driver can also be built as a module, if so, the module 1165 This driver can also be built as a module, if so, the module
1166 will be called "rtc-snvs". 1166 will be called "rtc-snvs".
1167 1167
1168comment "HID Sensor RTC drivers"
1169
1170config RTC_DRV_HID_SENSOR_TIME
1171 tristate "HID Sensor Time"
1172 depends on USB_HID
1173 select IIO
1174 select HID_SENSOR_HUB
1175 select HID_SENSOR_IIO_COMMON
1176 help
1177 Say yes here to build support for the HID Sensors of type Time.
1178 This drivers makes such sensors available as RTCs.
1179
1180 If this driver is compiled as a module, it will be named
1181 rtc-hid-sensor-time.
1182
1183
1168endif # RTC_CLASS 1184endif # RTC_CLASS
diff --git a/drivers/rtc/Makefile b/drivers/rtc/Makefile
index c3f62c80dc06..63be277d9ff6 100644
--- a/drivers/rtc/Makefile
+++ b/drivers/rtc/Makefile
@@ -52,6 +52,7 @@ obj-$(CONFIG_RTC_DRV_EM3027) += rtc-em3027.o
52obj-$(CONFIG_RTC_DRV_EP93XX) += rtc-ep93xx.o 52obj-$(CONFIG_RTC_DRV_EP93XX) += rtc-ep93xx.o
53obj-$(CONFIG_RTC_DRV_FM3130) += rtc-fm3130.o 53obj-$(CONFIG_RTC_DRV_FM3130) += rtc-fm3130.o
54obj-$(CONFIG_RTC_DRV_GENERIC) += rtc-generic.o 54obj-$(CONFIG_RTC_DRV_GENERIC) += rtc-generic.o
55obj-$(CONFIG_RTC_DRV_HID_SENSOR_TIME) += rtc-hid-sensor-time.o
55obj-$(CONFIG_RTC_DRV_IMXDI) += rtc-imxdi.o 56obj-$(CONFIG_RTC_DRV_IMXDI) += rtc-imxdi.o
56obj-$(CONFIG_RTC_DRV_ISL1208) += rtc-isl1208.o 57obj-$(CONFIG_RTC_DRV_ISL1208) += rtc-isl1208.o
57obj-$(CONFIG_RTC_DRV_ISL12022) += rtc-isl12022.o 58obj-$(CONFIG_RTC_DRV_ISL12022) += rtc-isl12022.o
diff --git a/drivers/rtc/rtc-hid-sensor-time.c b/drivers/rtc/rtc-hid-sensor-time.c
new file mode 100644
index 000000000000..25cac6ec374d
--- /dev/null
+++ b/drivers/rtc/rtc-hid-sensor-time.c
@@ -0,0 +1,291 @@
1/*
2 * HID Sensor Time Driver
3 * Copyright (c) 2012, Alexander Holler.
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/hid-sensor-hub.h>
23#include <linux/iio/iio.h>
24#include <linux/rtc.h>
25
26/* Format: HID-SENSOR-usage_id_in_hex */
27/* Usage ID from spec for Time: 0x2000A0 */
28#define DRIVER_NAME "HID-SENSOR-2000a0" /* must be lowercase */
29
30enum hid_time_channel {
31 CHANNEL_SCAN_INDEX_YEAR,
32 CHANNEL_SCAN_INDEX_MONTH,
33 CHANNEL_SCAN_INDEX_DAY,
34 CHANNEL_SCAN_INDEX_HOUR,
35 CHANNEL_SCAN_INDEX_MINUTE,
36 CHANNEL_SCAN_INDEX_SECOND,
37 TIME_RTC_CHANNEL_MAX,
38};
39
40struct hid_time_state {
41 struct hid_sensor_hub_callbacks callbacks;
42 struct hid_sensor_iio_common common_attributes;
43 struct hid_sensor_hub_attribute_info info[TIME_RTC_CHANNEL_MAX];
44 struct rtc_time last_time;
45 spinlock_t lock_last_time;
46 struct completion comp_last_time;
47 struct rtc_time time_buf;
48 struct rtc_device *rtc;
49};
50
51static const u32 hid_time_addresses[TIME_RTC_CHANNEL_MAX] = {
52 HID_USAGE_SENSOR_TIME_YEAR,
53 HID_USAGE_SENSOR_TIME_MONTH,
54 HID_USAGE_SENSOR_TIME_DAY,
55 HID_USAGE_SENSOR_TIME_HOUR,
56 HID_USAGE_SENSOR_TIME_MINUTE,
57 HID_USAGE_SENSOR_TIME_SECOND,
58};
59
60/* Channel names for verbose error messages */
61static const char * const hid_time_channel_names[TIME_RTC_CHANNEL_MAX] = {
62 "year", "month", "day", "hour", "minute", "second",
63};
64
65/* Callback handler to send event after all samples are received and captured */
66static int hid_time_proc_event(struct hid_sensor_hub_device *hsdev,
67 unsigned usage_id, void *priv)
68{
69 unsigned long flags;
70 struct hid_time_state *time_state = platform_get_drvdata(priv);
71
72 spin_lock_irqsave(&time_state->lock_last_time, flags);
73 time_state->last_time = time_state->time_buf;
74 spin_unlock_irqrestore(&time_state->lock_last_time, flags);
75 complete(&time_state->comp_last_time);
76 return 0;
77}
78
79static int hid_time_capture_sample(struct hid_sensor_hub_device *hsdev,
80 unsigned usage_id, size_t raw_len,
81 char *raw_data, void *priv)
82{
83 struct hid_time_state *time_state = platform_get_drvdata(priv);
84 struct rtc_time *time_buf = &time_state->time_buf;
85
86 switch (usage_id) {
87 case HID_USAGE_SENSOR_TIME_YEAR:
88 time_buf->tm_year = *(u8 *)raw_data;
89 if (time_buf->tm_year < 70)
90 /* assume we are in 1970...2069 */
91 time_buf->tm_year += 100;
92 break;
93 case HID_USAGE_SENSOR_TIME_MONTH:
94 /* sensor sending the month as 1-12, we need 0-11 */
95 time_buf->tm_mon = *(u8 *)raw_data-1;
96 break;
97 case HID_USAGE_SENSOR_TIME_DAY:
98 time_buf->tm_mday = *(u8 *)raw_data;
99 break;
100 case HID_USAGE_SENSOR_TIME_HOUR:
101 time_buf->tm_hour = *(u8 *)raw_data;
102 break;
103 case HID_USAGE_SENSOR_TIME_MINUTE:
104 time_buf->tm_min = *(u8 *)raw_data;
105 break;
106 case HID_USAGE_SENSOR_TIME_SECOND:
107 time_buf->tm_sec = *(u8 *)raw_data;
108 break;
109 default:
110 return -EINVAL;
111 }
112 return 0;
113}
114
115/* small helper, haven't found any other way */
116static const char *hid_time_attrib_name(u32 attrib_id)
117{
118 static const char unknown[] = "unknown";
119 unsigned i;
120
121 for (i = 0; i < TIME_RTC_CHANNEL_MAX; ++i) {
122 if (hid_time_addresses[i] == attrib_id)
123 return hid_time_channel_names[i];
124 }
125 return unknown; /* should never happen */
126}
127
128static int hid_time_parse_report(struct platform_device *pdev,
129 struct hid_sensor_hub_device *hsdev,
130 unsigned usage_id,
131 struct hid_time_state *time_state)
132{
133 int report_id, i;
134
135 for (i = 0; i < TIME_RTC_CHANNEL_MAX; ++i)
136 if (sensor_hub_input_get_attribute_info(hsdev,
137 HID_INPUT_REPORT, usage_id,
138 hid_time_addresses[i],
139 &time_state->info[i]) < 0)
140 return -EINVAL;
141 /* Check the (needed) attributes for sanity */
142 report_id = time_state->info[0].report_id;
143 if (report_id < 0) {
144 dev_err(&pdev->dev, "bad report ID!\n");
145 return -EINVAL;
146 }
147 for (i = 0; i < TIME_RTC_CHANNEL_MAX; ++i) {
148 if (time_state->info[i].report_id != report_id) {
149 dev_err(&pdev->dev,
150 "not all needed attributes inside the same report!\n");
151 return -EINVAL;
152 }
153 if (time_state->info[i].size != 1) {
154 dev_err(&pdev->dev,
155 "attribute '%s' not 8 bits wide!\n",
156 hid_time_attrib_name(
157 time_state->info[i].attrib_id));
158 return -EINVAL;
159 }
160 if (time_state->info[i].units !=
161 HID_USAGE_SENSOR_UNITS_NOT_SPECIFIED &&
162 /* allow attribute seconds with unit seconds */
163 !(time_state->info[i].attrib_id ==
164 HID_USAGE_SENSOR_TIME_SECOND &&
165 time_state->info[i].units ==
166 HID_USAGE_SENSOR_UNITS_SECOND)) {
167 dev_err(&pdev->dev,
168 "attribute '%s' hasn't a unit of type 'none'!\n",
169 hid_time_attrib_name(
170 time_state->info[i].attrib_id));
171 return -EINVAL;
172 }
173 if (time_state->info[i].unit_expo) {
174 dev_err(&pdev->dev,
175 "attribute '%s' hasn't a unit exponent of 1!\n",
176 hid_time_attrib_name(
177 time_state->info[i].attrib_id));
178 return -EINVAL;
179 }
180 }
181
182 return 0;
183}
184
185static int hid_rtc_read_time(struct device *dev, struct rtc_time *tm)
186{
187 unsigned long flags;
188 struct hid_time_state *time_state =
189 platform_get_drvdata(to_platform_device(dev));
190 int ret;
191
192 INIT_COMPLETION(time_state->comp_last_time);
193 /* get a report with all values through requesting one value */
194 sensor_hub_input_attr_get_raw_value(time_state->common_attributes.hsdev,
195 HID_USAGE_SENSOR_TIME, hid_time_addresses[0],
196 time_state->info[0].report_id);
197 /* wait for all values (event) */
198 ret = wait_for_completion_killable_timeout(
199 &time_state->comp_last_time, HZ*6);
200 if (ret > 0) {
201 /* no error */
202 spin_lock_irqsave(&time_state->lock_last_time, flags);
203 *tm = time_state->last_time;
204 spin_unlock_irqrestore(&time_state->lock_last_time, flags);
205 return 0;
206 }
207 if (!ret)
208 return -EIO; /* timeouted */
209 return ret; /* killed (-ERESTARTSYS) */
210}
211
212static const struct rtc_class_ops hid_time_rtc_ops = {
213 .read_time = hid_rtc_read_time,
214};
215
216static int hid_time_probe(struct platform_device *pdev)
217{
218 int ret = 0;
219 struct hid_sensor_hub_device *hsdev = pdev->dev.platform_data;
220 struct hid_time_state *time_state = devm_kzalloc(&pdev->dev,
221 sizeof(struct hid_time_state), GFP_KERNEL);
222
223 if (time_state == NULL)
224 return -ENOMEM;
225
226 platform_set_drvdata(pdev, time_state);
227
228 init_completion(&time_state->comp_last_time);
229 time_state->common_attributes.hsdev = hsdev;
230 time_state->common_attributes.pdev = pdev;
231
232 ret = hid_sensor_parse_common_attributes(hsdev,
233 HID_USAGE_SENSOR_TIME,
234 &time_state->common_attributes);
235 if (ret) {
236 dev_err(&pdev->dev, "failed to setup common attributes!\n");
237 return ret;
238 }
239
240 ret = hid_time_parse_report(pdev, hsdev, HID_USAGE_SENSOR_TIME,
241 time_state);
242 if (ret) {
243 dev_err(&pdev->dev, "failed to setup attributes!\n");
244 return ret;
245 }
246
247 time_state->callbacks.send_event = hid_time_proc_event;
248 time_state->callbacks.capture_sample = hid_time_capture_sample;
249 time_state->callbacks.pdev = pdev;
250 ret = sensor_hub_register_callback(hsdev, HID_USAGE_SENSOR_TIME,
251 &time_state->callbacks);
252 if (ret < 0) {
253 dev_err(&pdev->dev, "register callback failed!\n");
254 return ret;
255 }
256
257 time_state->rtc = rtc_device_register("hid-sensor-time",
258 &pdev->dev, &hid_time_rtc_ops, THIS_MODULE);
259
260 if (IS_ERR(time_state->rtc)) {
261 dev_err(&pdev->dev, "rtc device register failed!\n");
262 return PTR_ERR(time_state->rtc);
263 }
264
265 return ret;
266}
267
268static int hid_time_remove(struct platform_device *pdev)
269{
270 struct hid_sensor_hub_device *hsdev = pdev->dev.platform_data;
271 struct hid_time_state *time_state = platform_get_drvdata(pdev);
272
273 rtc_device_unregister(time_state->rtc);
274 sensor_hub_remove_callback(hsdev, HID_USAGE_SENSOR_TIME);
275
276 return 0;
277}
278
279static struct platform_driver hid_time_platform_driver = {
280 .driver = {
281 .name = DRIVER_NAME,
282 .owner = THIS_MODULE,
283 },
284 .probe = hid_time_probe,
285 .remove = hid_time_remove,
286};
287module_platform_driver(hid_time_platform_driver);
288
289MODULE_DESCRIPTION("HID Sensor Time");
290MODULE_AUTHOR("Alexander Holler <holler@ahsoftware.de>");
291MODULE_LICENSE("GPL");