On 12/09/2012 01:21 PM, Alexander Holler wrote: > This driver makes the time from HID sensors (hubs) which are offering > such available like any other RTC does. > > Currently the time can only be read. Setting the time must be done > through sending a report, which currently isn't supported by > hid-sensor-hub. > > 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. I don't think we should register a IIO device for this. Just an RTC device should be fully sufficient. > > Signed-off-by: Alexander Holler <holler@xxxxxxxxxxxxx> > --- > drivers/iio/Kconfig | 1 + > drivers/iio/Makefile | 1 + > drivers/iio/time/Kconfig | 14 ++ > drivers/iio/time/Makefile | 5 + > drivers/iio/time/hid-sensor-time.c | 397 ++++++++++++++++++++++++++++++++++++ > 5 files changed, 418 insertions(+), 0 deletions(-) > create mode 100644 drivers/iio/time/Kconfig > create mode 100644 drivers/iio/time/Makefile > create mode 100644 drivers/iio/time/hid-sensor-time.c > > diff --git a/drivers/iio/Kconfig b/drivers/iio/Kconfig > index fc937ac..78fa3ff 100644 > --- a/drivers/iio/Kconfig > +++ b/drivers/iio/Kconfig > @@ -63,5 +63,6 @@ source "drivers/iio/dac/Kconfig" > source "drivers/iio/common/Kconfig" > source "drivers/iio/gyro/Kconfig" > source "drivers/iio/magnetometer/Kconfig" > +source "drivers/iio/time/Kconfig" > > endif # IIO > diff --git a/drivers/iio/Makefile b/drivers/iio/Makefile > index 761f2b6..6a6da31 100644 > --- a/drivers/iio/Makefile > +++ b/drivers/iio/Makefile > @@ -19,3 +19,4 @@ obj-y += dac/ > obj-y += common/ > obj-y += gyro/ > obj-y += magnetometer/ > +obj-y += time/ > diff --git a/drivers/iio/time/Kconfig b/drivers/iio/time/Kconfig > new file mode 100644 > index 0000000..0ca4682 > --- /dev/null > +++ b/drivers/iio/time/Kconfig > @@ -0,0 +1,14 @@ > +# > +# Time sensors > +# > +menu "Time sensors" > + > +config HID_SENSOR_TIME > + depends on HID_SENSOR_HUB > + select HID_SENSOR_IIO_COMMON > + tristate "HID Time" > + help > + Say yes here to build support for the HID SENSOR Time. > + This drivers makes such sensors available as RTCs. > + > +endmenu > diff --git a/drivers/iio/time/Makefile b/drivers/iio/time/Makefile > new file mode 100644 > index 0000000..705fe0d > --- /dev/null > +++ b/drivers/iio/time/Makefile > @@ -0,0 +1,5 @@ > +# > +# Makefile for industrial I/O Time sensor driver > +# > + > +obj-$(CONFIG_HID_SENSOR_TIME) += hid-sensor-time.o > diff --git a/drivers/iio/time/hid-sensor-time.c b/drivers/iio/time/hid-sensor-time.c > new file mode 100644 > index 0000000..a5993d6 > --- /dev/null > +++ b/drivers/iio/time/hid-sensor-time.c > @@ -0,0 +1,397 @@ > +/* > + * HID Sensor Time Driver > + * Copyright (c) 2012, Alexander Holler. > + * > + * This program is free software; you can redistribute it and/or modify it > + * under the terms and conditions of the GNU General Public License, > + * version 2, as published by the Free Software Foundation. > + * > + * This program is distributed in the hope it will be useful, but WITHOUT > + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or > + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for > + * more details. > + * > + * You should have received a copy of the GNU General Public License along with > + * this program; if not, write to the Free Software Foundation, Inc., > + * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. > + * > + */ > +#include <linux/device.h> > +#include <linux/platform_device.h> > +#include <linux/module.h> > +#include <linux/hid-sensor-hub.h> > +#include <linux/iio/iio.h> > +#include <linux/rtc.h> > +#include "../common/hid-sensors/hid-sensor-attributes.h" > + > +/* Format: HID-SENSOR-usage_id_in_hex */ > +/* Usage ID from spec for Time: 0x2000A0 */ > +#define DRIVER_NAME "HID-SENSOR-2000a0" /* must be lowercase */ > + > +enum hid_time_channel { > + CHANNEL_SCAN_INDEX_YEAR, > + CHANNEL_SCAN_INDEX_MONTH, > + CHANNEL_SCAN_INDEX_DAY, > + CHANNEL_SCAN_INDEX_HOUR, > + CHANNEL_SCAN_INDEX_MINUTE, > + CHANNEL_SCAN_INDEX_SECOND, > + TIME_RTC_CHANNEL_MAX, > +}; > + > +struct hid_time_state { > + struct hid_sensor_hub_callbacks callbacks; > + struct hid_sensor_iio_common common_attributes; > + struct hid_sensor_hub_attribute_info info[TIME_RTC_CHANNEL_MAX]; > + struct rtc_time last_time; > + spinlock_t lock_last_time; > + struct completion comp_last_time; > + struct rtc_time time_buf; > + struct rtc_device *rtc; > +}; > + > +static const u32 hid_time_addresses[TIME_RTC_CHANNEL_MAX] = { > + HID_USAGE_SENSOR_TIME_YEAR, > + HID_USAGE_SENSOR_TIME_MONTH, > + HID_USAGE_SENSOR_TIME_DAY, > + HID_USAGE_SENSOR_TIME_HOUR, > + HID_USAGE_SENSOR_TIME_MINUTE, > + HID_USAGE_SENSOR_TIME_SECOND, > +}; > + > +/* Channel definitions */ > +static const struct iio_chan_spec hid_time_channels[TIME_RTC_CHANNEL_MAX] = { > + { > + .info_mask = IIO_CHAN_INFO_RAW, > + .scan_index = CHANNEL_SCAN_INDEX_YEAR, > + .extend_name = "year", > + }, { > + .info_mask = IIO_CHAN_INFO_RAW, > + .scan_index = CHANNEL_SCAN_INDEX_MONTH, > + .extend_name = "month", > + }, { > + .info_mask = IIO_CHAN_INFO_RAW, > + .scan_index = CHANNEL_SCAN_INDEX_DAY, > + .extend_name = "day", > + }, { > + .info_mask = IIO_CHAN_INFO_RAW, > + .scan_index = CHANNEL_SCAN_INDEX_HOUR, > + .extend_name = "hour", > + }, { > + .info_mask = IIO_CHAN_INFO_RAW, > + .scan_index = CHANNEL_SCAN_INDEX_MINUTE, > + .extend_name = "minute", > + }, { > + .info_mask = IIO_CHAN_INFO_RAW, > + .scan_index = CHANNEL_SCAN_INDEX_SECOND, > + .extend_name = "second", > + } > +}; > + > +/* Adjust channel real bits based on report descriptor */ > +static void hid_time_adjust_channel_bit_mask(struct iio_chan_spec *channels, > + int channel, int size) > +{ > + channels[channel].scan_type.sign = 'u'; > + /* Real storage bits will change based on the report desc. */ > + channels[channel].scan_type.realbits = size * 8; > + /* Maximum size of a sample to capture is u8 */ > + channels[channel].scan_type.storagebits = sizeof(u8) * 8; > +} > + > +static int hid_time_read_raw(struct iio_dev *indio_dev, > + struct iio_chan_spec const *chan, > + int *val, int *val2, > + long mask) > +{ > + struct hid_time_state *time_state = iio_priv(indio_dev); > + int report_id; > + u32 address; > + > + *val = 0; > + *val2 = 0; > + if(mask) > + return -EINVAL; > + report_id = time_state->info[chan->scan_index].report_id; > + address = hid_time_addresses[chan->scan_index]; > + if (report_id >= 0) { > + *val = sensor_hub_input_attr_get_raw_value( > + time_state->common_attributes.hsdev, > + HID_USAGE_SENSOR_TIME, address, report_id); > + return IIO_VAL_INT; > + } > + *val = 0; > + return -EINVAL; > +} > + > +/* Callback handler to send event after all samples are received and captured */ > +static int hid_time_proc_event(struct hid_sensor_hub_device *hsdev, > + unsigned usage_id, void *priv) > +{ > + unsigned long flags; > + struct iio_dev *indio_dev = platform_get_drvdata(priv); > + struct hid_time_state *time_state = iio_priv(indio_dev); > + > + spin_lock_irqsave(&time_state->lock_last_time, flags); > + time_state->last_time = time_state->time_buf; > + spin_unlock_irqrestore(&time_state->lock_last_time, flags); > + complete(&time_state->comp_last_time); > + return 0; > +} > + > +static int hid_time_capture_sample(struct hid_sensor_hub_device *hsdev, > + unsigned usage_id, size_t raw_len, > + char *raw_data, void *priv) > +{ > + struct iio_dev *indio_dev = platform_get_drvdata(priv); > + struct hid_time_state *time_state = iio_priv(indio_dev); > + struct rtc_time *time_buf = &time_state->time_buf; > + > + switch (usage_id) { > + case HID_USAGE_SENSOR_TIME_YEAR: > + time_buf->tm_year = *(u8 *)raw_data; > + if (time_buf->tm_year < 70) > + /* assume we are in 1970...2069 */ > + time_buf->tm_year += 100; > + break; > + case HID_USAGE_SENSOR_TIME_MONTH: > + time_buf->tm_mon = --*(u8 *)raw_data; > + break; > + case HID_USAGE_SENSOR_TIME_DAY: > + time_buf->tm_mday = *(u8 *)raw_data; > + break; > + case HID_USAGE_SENSOR_TIME_HOUR: > + time_buf->tm_hour = *(u8 *)raw_data; > + break; > + case HID_USAGE_SENSOR_TIME_MINUTE: > + time_buf->tm_min = *(u8 *)raw_data; > + break; > + case HID_USAGE_SENSOR_TIME_SECOND: > + time_buf->tm_sec = *(u8 *)raw_data; > + break; > + default: > + return -EINVAL; > + } > + return 0; > +} > + > +static int hid_time_parse_report(struct platform_device *pdev, > + struct hid_sensor_hub_device *hsdev, > + struct iio_chan_spec *channels, > + unsigned usage_id, struct hid_time_state *st) > +{ > + int ret, i=0; > + > + for (; i < TIME_RTC_CHANNEL_MAX; ++i) { > + ret = sensor_hub_input_get_attribute_info(hsdev, > + HID_INPUT_REPORT, usage_id, > + hid_time_addresses[i], &st->info[i]); > + if (ret < 0) > + return ret; > + hid_time_adjust_channel_bit_mask(channels, i, > + st->info[i].size); > + } > + > + return ret; > +} > + > +static int hid_rtc_read_time(struct device *dev, > + struct rtc_time *tm) > +{ > + int val; > + unsigned long flags; > + struct iio_dev *indio_dev = > + platform_get_drvdata(to_platform_device(dev)); > + struct hid_time_state *time_state = iio_priv(indio_dev); > + > + init_completion(&time_state->comp_last_time); > + /* start a read */ > + if (hid_time_read_raw(indio_dev, > + &hid_time_channels[0], &val, &val, 0) == -EINVAL) { > + dev_err(dev, "unable to read time!\n"); > + return -EIO; > + } > + /* wait for all values (event) */ > + wait_for_completion_interruptible_timeout(&time_state->comp_last_time, > + HZ*6); > + spin_lock_irqsave(&time_state->lock_last_time, flags); > + *tm = time_state->last_time; > + spin_unlock_irqrestore(&time_state->lock_last_time, flags); > + > + return 0; > +} > + > +/* small helper, haven't found any other way */ > +static const char *attrib_name(u32 attrib_id) > +{ > + unsigned i=0; > + static const char unknown[] = "unknown"; > + > + for(; i< TIME_RTC_CHANNEL_MAX; ++i) { > + if(hid_time_addresses[i] == attrib_id) > + return hid_time_channels[i].extend_name; > + } > + return unknown; /* should never happen */ > +} > + > +static const struct iio_info hid_time_info = { > + .driver_module = THIS_MODULE, > + .read_raw = &hid_time_read_raw, > +}; > + > +static const struct rtc_class_ops rtc_ops = { > + .read_time = hid_rtc_read_time, > +}; > + > +static int __devinit hid_time_probe(struct platform_device *pdev) > +{ > + int temp, ret = 0; > + static char *name = "hid_time"; > + struct hid_time_state *time_state; > + struct iio_chan_spec *channels; > + struct hid_sensor_hub_device *hsdev = pdev->dev.platform_data; > + struct iio_dev *indio_dev = iio_device_alloc(sizeof(struct hid_time_state)); > + > + if (indio_dev == NULL) { > + ret = -ENOMEM; > + goto error_ret; > + } > + platform_set_drvdata(pdev, indio_dev); > + > + time_state = iio_priv(indio_dev); > + time_state->common_attributes.hsdev = hsdev; > + time_state->common_attributes.pdev = pdev; > + > + ret = hid_sensor_parse_common_attributes(hsdev, > + HID_USAGE_SENSOR_TIME, > + &time_state->common_attributes); > + if (ret) { > + dev_err(&pdev->dev, "failed to setup common attributes!\n"); > + goto error_free_dev; > + } > + > + channels = kmemdup(hid_time_channels, > + sizeof(hid_time_channels), > + GFP_KERNEL); > + if (!channels) { > + dev_err(&pdev->dev, "failed to duplicate channels!\n"); > + goto error_free_dev; > + } > + > + ret = hid_time_parse_report(pdev, hsdev, channels, > + HID_USAGE_SENSOR_TIME, time_state); > + if (ret) { > + dev_err(&pdev->dev, "failed to setup attributes!\n"); > + goto error_free_dev_mem; > + } > + > + /* Check the attributes we need for sanity */ > + temp = time_state->info[ret].report_id; > + if (temp<0) { > + dev_err(&pdev->dev, "bad report ID!\n"); > + goto error_free_dev_mem; > + } > + for (ret = 0; ret< TIME_RTC_CHANNEL_MAX; ++ret) { > + if (time_state->info[ret].report_id != temp) { > + dev_err(&pdev->dev, > + "not all needed attributes inside the same report!\n"); > + goto error_free_dev_mem; > + } > + if (time_state->info[ret].size != 1) { > + dev_err(&pdev->dev, > + "attribute '%s' not 8 bits wide!\n", > + attrib_name(time_state->info[ret].attrib_id)); > + goto error_free_dev_mem; > + } > + if (time_state->info[ret].units != > + HID_USAGE_SENSOR_UNITS_NOT_SPECIFIED && > + /* allow attribute seconds with unit seconds */ > + !(time_state->info[ret].attrib_id == > + HID_USAGE_SENSOR_TIME_SECOND && > + time_state->info[ret].units == > + HID_USAGE_SENSOR_UNITS_SECOND)) { > + dev_err(&pdev->dev, > + "attribute '%s' hasn't a unit of type 'none'!\n", > + attrib_name(time_state->info[ret].attrib_id)); > + goto error_free_dev_mem; > + } > + if (time_state->info[ret].unit_expo) { > + dev_err(&pdev->dev, > + "attribute '%s' hasn't a unit exponent of 1!\n", > + attrib_name(time_state->info[ret].attrib_id)); > + goto error_free_dev_mem; > + } > + } > + > + indio_dev->channels = channels; > + indio_dev->num_channels = ARRAY_SIZE(hid_time_channels); > + indio_dev->dev.parent = &pdev->dev; > + indio_dev->info = &hid_time_info; > + indio_dev->name = name; > + indio_dev->modes = INDIO_DIRECT_MODE; > + > + ret = iio_device_register(indio_dev); > + if (ret) { > + dev_err(&pdev->dev, "device register failed!\n"); > + goto error_free_dev_mem; > + } > + > + time_state->callbacks.send_event = hid_time_proc_event; > + time_state->callbacks.capture_sample = hid_time_capture_sample; > + time_state->callbacks.pdev = pdev; > + ret = sensor_hub_register_callback(hsdev, HID_USAGE_SENSOR_TIME, > + &time_state->callbacks); > + if (ret < 0) { > + dev_err(&pdev->dev, "register callback failed!\n"); > + goto error_iio_unreg; > + } > + > + time_state->rtc = rtc_device_register("hid-sensor-time", > + &pdev->dev, &rtc_ops, THIS_MODULE); > + > + if (IS_ERR(time_state->rtc)) { > + ret = PTR_ERR(time_state->rtc); > + dev_err(&pdev->dev, "rtc device register failed!\n"); > + goto error_iio_unreg; > + } > + > + return ret; > + > +error_iio_unreg: > + iio_device_unregister(indio_dev); > +error_free_dev_mem: > + kfree(indio_dev->channels); > +error_free_dev: > + iio_device_free(indio_dev); > +error_ret: > + return ret; > +} > + > +static int __devinit hid_time_remove(struct platform_device *pdev) > +{ > + struct hid_sensor_hub_device *hsdev = pdev->dev.platform_data; > + struct iio_dev *indio_dev = platform_get_drvdata(pdev); > + struct hid_time_state *time_state = iio_priv(indio_dev); > + > + if (!IS_ERR(time_state->rtc)) > + rtc_device_unregister(time_state->rtc); > + sensor_hub_remove_callback(hsdev, HID_USAGE_SENSOR_TIME); > + iio_device_unregister(indio_dev); > + kfree(indio_dev->channels); > + iio_device_free(indio_dev); > + > + return 0; > +} > + > +static struct platform_driver hid_time_platform_driver = { > + .driver = { > + .name = DRIVER_NAME, > + .owner = THIS_MODULE, > + }, > + .probe = hid_time_probe, > + .remove = hid_time_remove, > +}; > +module_platform_driver(hid_time_platform_driver); > + > +MODULE_DESCRIPTION("HID Sensor Time"); > +MODULE_AUTHOR("Alexander Holler <holler@xxxxxxxxxxxxx>"); > +MODULE_LICENSE("GPL"); -- To unsubscribe from this list: send the line "unsubscribe linux-iio" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html