remarkable-linux/drivers/staging/greybus/hid.c
Greg Kroah-Hartman be21106d67 staging: greybus: hid: remove KERNEL_VERSION checks
No need to support older kernel versions in the Greybus HID driver, so
remove the checks as needed, we can now rely on all of the "new" apis
being present.

Signed-off-by: Greg Kroah-Hartman <gregkh@google.com>
2016-09-19 16:30:41 +02:00

537 lines
12 KiB
C

/*
* HID class driver for the Greybus.
*
* Copyright 2014 Google Inc.
* Copyright 2014 Linaro Ltd.
*
* Released under the GPLv2 only.
*/
#include <linux/bitops.h>
#include <linux/hid.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/slab.h>
#include "greybus.h"
/* Greybus HID device's structure */
struct gb_hid {
struct gb_bundle *bundle;
struct gb_connection *connection;
struct hid_device *hid;
struct gb_hid_desc_response hdesc;
unsigned long flags;
#define GB_HID_STARTED 0x01
#define GB_HID_READ_PENDING 0x04
unsigned int bufsize;
char *inbuf;
};
static DEFINE_MUTEX(gb_hid_open_mutex);
/* Routines to get controller's information over greybus */
/* Operations performed on greybus */
static int gb_hid_get_desc(struct gb_hid *ghid)
{
return gb_operation_sync(ghid->connection, GB_HID_TYPE_GET_DESC, NULL,
0, &ghid->hdesc, sizeof(ghid->hdesc));
}
static int gb_hid_get_report_desc(struct gb_hid *ghid, char *rdesc)
{
int ret;
ret = gb_pm_runtime_get_sync(ghid->bundle);
if (ret)
return ret;
ret = gb_operation_sync(ghid->connection, GB_HID_TYPE_GET_REPORT_DESC,
NULL, 0, rdesc,
le16_to_cpu(ghid->hdesc.wReportDescLength));
gb_pm_runtime_put_autosuspend(ghid->bundle);
return ret;
}
static int gb_hid_set_power(struct gb_hid *ghid, int type)
{
int ret;
ret = gb_pm_runtime_get_sync(ghid->bundle);
if (ret)
return ret;
ret = gb_operation_sync(ghid->connection, type, NULL, 0, NULL, 0);
gb_pm_runtime_put_autosuspend(ghid->bundle);
return ret;
}
static int gb_hid_get_report(struct gb_hid *ghid, u8 report_type, u8 report_id,
unsigned char *buf, int len)
{
struct gb_hid_get_report_request request;
int ret;
ret = gb_pm_runtime_get_sync(ghid->bundle);
if (ret)
return ret;
request.report_type = report_type;
request.report_id = report_id;
ret = gb_operation_sync(ghid->connection, GB_HID_TYPE_GET_REPORT,
&request, sizeof(request), buf, len);
gb_pm_runtime_put_autosuspend(ghid->bundle);
return ret;
}
static int gb_hid_set_report(struct gb_hid *ghid, u8 report_type, u8 report_id,
unsigned char *buf, int len)
{
struct gb_hid_set_report_request *request;
struct gb_operation *operation;
int ret, size = sizeof(*request) + len - 1;
ret = gb_pm_runtime_get_sync(ghid->bundle);
if (ret)
return ret;
operation = gb_operation_create(ghid->connection,
GB_HID_TYPE_SET_REPORT, size, 0,
GFP_KERNEL);
if (!operation) {
gb_pm_runtime_put_autosuspend(ghid->bundle);
return -ENOMEM;
}
request = operation->request->payload;
request->report_type = report_type;
request->report_id = report_id;
memcpy(request->report, buf, len);
ret = gb_operation_request_send_sync(operation);
if (ret) {
dev_err(&operation->connection->bundle->dev,
"failed to set report: %d\n", ret);
} else {
ret = len;
}
gb_operation_put(operation);
gb_pm_runtime_put_autosuspend(ghid->bundle);
return ret;
}
static int gb_hid_request_handler(struct gb_operation *op)
{
struct gb_connection *connection = op->connection;
struct gb_hid *ghid = gb_connection_get_data(connection);
struct gb_hid_input_report_request *request = op->request->payload;
if (op->type != GB_HID_TYPE_IRQ_EVENT) {
dev_err(&connection->bundle->dev,
"unsupported unsolicited request\n");
return -EINVAL;
}
if (test_bit(GB_HID_STARTED, &ghid->flags))
hid_input_report(ghid->hid, HID_INPUT_REPORT,
request->report, op->request->payload_size, 1);
return 0;
}
static int gb_hid_report_len(struct hid_report *report)
{
return ((report->size - 1) >> 3) + 1 +
report->device->report_enum[report->type].numbered;
}
static void gb_hid_find_max_report(struct hid_device *hid, unsigned int type,
unsigned int *max)
{
struct hid_report *report;
unsigned int size;
list_for_each_entry(report, &hid->report_enum[type].report_list, list) {
size = gb_hid_report_len(report);
if (*max < size)
*max = size;
}
}
static void gb_hid_free_buffers(struct gb_hid *ghid)
{
kfree(ghid->inbuf);
ghid->inbuf = NULL;
ghid->bufsize = 0;
}
static int gb_hid_alloc_buffers(struct gb_hid *ghid, size_t bufsize)
{
ghid->inbuf = kzalloc(bufsize, GFP_KERNEL);
if (!ghid->inbuf)
return -ENOMEM;
ghid->bufsize = bufsize;
return 0;
}
/* Routines dealing with reports */
static void gb_hid_init_report(struct gb_hid *ghid, struct hid_report *report)
{
unsigned int size;
size = gb_hid_report_len(report);
if (gb_hid_get_report(ghid, report->type, report->id, ghid->inbuf,
size))
return;
/*
* hid->driver_lock is held as we are in probe function,
* we just need to setup the input fields, so using
* hid_report_raw_event is safe.
*/
hid_report_raw_event(ghid->hid, report->type, ghid->inbuf, size, 1);
}
static void gb_hid_init_reports(struct gb_hid *ghid)
{
struct hid_device *hid = ghid->hid;
struct hid_report *report;
list_for_each_entry(report,
&hid->report_enum[HID_INPUT_REPORT].report_list, list)
gb_hid_init_report(ghid, report);
list_for_each_entry(report,
&hid->report_enum[HID_FEATURE_REPORT].report_list, list)
gb_hid_init_report(ghid, report);
}
static int __gb_hid_get_raw_report(struct hid_device *hid,
unsigned char report_number, __u8 *buf, size_t count,
unsigned char report_type)
{
struct gb_hid *ghid = hid->driver_data;
int ret;
if (report_type == HID_OUTPUT_REPORT)
return -EINVAL;
ret = gb_hid_get_report(ghid, report_type, report_number, buf, count);
if (!ret)
ret = count;
return ret;
}
static int __gb_hid_output_raw_report(struct hid_device *hid, __u8 *buf,
size_t len, unsigned char report_type)
{
struct gb_hid *ghid = hid->driver_data;
int report_id = buf[0];
int ret;
if (report_type == HID_INPUT_REPORT)
return -EINVAL;
if (report_id) {
buf++;
len--;
}
ret = gb_hid_set_report(ghid, report_type, report_id, buf, len);
if (report_id && ret >= 0)
ret++; /* add report_id to the number of transfered bytes */
return 0;
}
static int gb_hid_raw_request(struct hid_device *hid, unsigned char reportnum,
__u8 *buf, size_t len, unsigned char rtype,
int reqtype)
{
switch (reqtype) {
case HID_REQ_GET_REPORT:
return __gb_hid_get_raw_report(hid, reportnum, buf, len, rtype);
case HID_REQ_SET_REPORT:
if (buf[0] != reportnum)
return -EINVAL;
return __gb_hid_output_raw_report(hid, buf, len, rtype);
default:
return -EIO;
}
}
/* HID Callbacks */
static int gb_hid_parse(struct hid_device *hid)
{
struct gb_hid *ghid = hid->driver_data;
unsigned int rsize;
char *rdesc;
int ret;
rsize = le16_to_cpu(ghid->hdesc.wReportDescLength);
if (!rsize || rsize > HID_MAX_DESCRIPTOR_SIZE) {
dbg_hid("weird size of report descriptor (%u)\n", rsize);
return -EINVAL;
}
rdesc = kzalloc(rsize, GFP_KERNEL);
if (!rdesc) {
dbg_hid("couldn't allocate rdesc memory\n");
return -ENOMEM;
}
ret = gb_hid_get_report_desc(ghid, rdesc);
if (ret) {
hid_err(hid, "reading report descriptor failed\n");
goto free_rdesc;
}
ret = hid_parse_report(hid, rdesc, rsize);
if (ret)
dbg_hid("parsing report descriptor failed\n");
free_rdesc:
kfree(rdesc);
return ret;
}
static int gb_hid_start(struct hid_device *hid)
{
struct gb_hid *ghid = hid->driver_data;
unsigned int bufsize = HID_MIN_BUFFER_SIZE;
int ret;
gb_hid_find_max_report(hid, HID_INPUT_REPORT, &bufsize);
gb_hid_find_max_report(hid, HID_OUTPUT_REPORT, &bufsize);
gb_hid_find_max_report(hid, HID_FEATURE_REPORT, &bufsize);
if (bufsize > HID_MAX_BUFFER_SIZE)
bufsize = HID_MAX_BUFFER_SIZE;
ret = gb_hid_alloc_buffers(ghid, bufsize);
if (ret)
return ret;
if (!(hid->quirks & HID_QUIRK_NO_INIT_REPORTS))
gb_hid_init_reports(ghid);
return 0;
}
static void gb_hid_stop(struct hid_device *hid)
{
struct gb_hid *ghid = hid->driver_data;
gb_hid_free_buffers(ghid);
}
static int gb_hid_open(struct hid_device *hid)
{
struct gb_hid *ghid = hid->driver_data;
int ret = 0;
mutex_lock(&gb_hid_open_mutex);
if (!hid->open++) {
ret = gb_hid_set_power(ghid, GB_HID_TYPE_PWR_ON);
if (ret < 0)
hid->open--;
else
set_bit(GB_HID_STARTED, &ghid->flags);
}
mutex_unlock(&gb_hid_open_mutex);
return ret;
}
static void gb_hid_close(struct hid_device *hid)
{
struct gb_hid *ghid = hid->driver_data;
int ret;
/*
* Protecting hid->open to make sure we don't restart data acquistion
* due to a resumption we no longer care about..
*/
mutex_lock(&gb_hid_open_mutex);
if (!--hid->open) {
clear_bit(GB_HID_STARTED, &ghid->flags);
/* Save some power */
ret = gb_hid_set_power(ghid, GB_HID_TYPE_PWR_OFF);
if (ret)
dev_err(&ghid->connection->bundle->dev,
"failed to power off (%d)\n", ret);
}
mutex_unlock(&gb_hid_open_mutex);
}
static int gb_hid_power(struct hid_device *hid, int lvl)
{
struct gb_hid *ghid = hid->driver_data;
switch (lvl) {
case PM_HINT_FULLON:
return gb_hid_set_power(ghid, GB_HID_TYPE_PWR_ON);
case PM_HINT_NORMAL:
return gb_hid_set_power(ghid, GB_HID_TYPE_PWR_OFF);
}
return 0;
}
/* HID structure to pass callbacks */
static struct hid_ll_driver gb_hid_ll_driver = {
.parse = gb_hid_parse,
.start = gb_hid_start,
.stop = gb_hid_stop,
.open = gb_hid_open,
.close = gb_hid_close,
.power = gb_hid_power,
.raw_request = gb_hid_raw_request,
};
static int gb_hid_init(struct gb_hid *ghid)
{
struct hid_device *hid = ghid->hid;
int ret;
ret = gb_hid_get_desc(ghid);
if (ret)
return ret;
hid->version = le16_to_cpu(ghid->hdesc.bcdHID);
hid->vendor = le16_to_cpu(ghid->hdesc.wVendorID);
hid->product = le16_to_cpu(ghid->hdesc.wProductID);
hid->country = ghid->hdesc.bCountryCode;
hid->driver_data = ghid;
hid->ll_driver = &gb_hid_ll_driver;
hid->dev.parent = &ghid->connection->bundle->dev;
// hid->bus = BUS_GREYBUS; /* Need a bustype for GREYBUS in <linux/input.h> */
/* Set HID device's name */
snprintf(hid->name, sizeof(hid->name), "%s %04X:%04X",
dev_name(&ghid->connection->bundle->dev),
hid->vendor, hid->product);
return 0;
}
static int gb_hid_probe(struct gb_bundle *bundle,
const struct greybus_bundle_id *id)
{
struct greybus_descriptor_cport *cport_desc;
struct gb_connection *connection;
struct hid_device *hid;
struct gb_hid *ghid;
int ret;
if (bundle->num_cports != 1)
return -ENODEV;
cport_desc = &bundle->cport_desc[0];
if (cport_desc->protocol_id != GREYBUS_PROTOCOL_HID)
return -ENODEV;
ghid = kzalloc(sizeof(*ghid), GFP_KERNEL);
if (!ghid)
return -ENOMEM;
connection = gb_connection_create(bundle, le16_to_cpu(cport_desc->id),
gb_hid_request_handler);
if (IS_ERR(connection)) {
ret = PTR_ERR(connection);
goto err_free_ghid;
}
gb_connection_set_data(connection, ghid);
ghid->connection = connection;
hid = hid_allocate_device();
if (IS_ERR(hid)) {
ret = PTR_ERR(hid);
goto err_connection_destroy;
}
ghid->hid = hid;
ghid->bundle = bundle;
greybus_set_drvdata(bundle, ghid);
ret = gb_connection_enable(connection);
if (ret)
goto err_destroy_hid;
ret = gb_hid_init(ghid);
if (ret)
goto err_connection_disable;
ret = hid_add_device(hid);
if (ret) {
hid_err(hid, "can't add hid device: %d\n", ret);
goto err_connection_disable;
}
gb_pm_runtime_put_autosuspend(bundle);
return 0;
err_connection_disable:
gb_connection_disable(connection);
err_destroy_hid:
hid_destroy_device(hid);
err_connection_destroy:
gb_connection_destroy(connection);
err_free_ghid:
kfree(ghid);
return ret;
}
static void gb_hid_disconnect(struct gb_bundle *bundle)
{
struct gb_hid *ghid = greybus_get_drvdata(bundle);
if (gb_pm_runtime_get_sync(bundle))
gb_pm_runtime_get_noresume(bundle);
hid_destroy_device(ghid->hid);
gb_connection_disable(ghid->connection);
gb_connection_destroy(ghid->connection);
kfree(ghid);
}
static const struct greybus_bundle_id gb_hid_id_table[] = {
{ GREYBUS_DEVICE_CLASS(GREYBUS_CLASS_HID) },
{ }
};
MODULE_DEVICE_TABLE(greybus, gb_hid_id_table);
static struct greybus_driver gb_hid_driver = {
.name = "hid",
.probe = gb_hid_probe,
.disconnect = gb_hid_disconnect,
.id_table = gb_hid_id_table,
};
module_greybus_driver(gb_hid_driver);
MODULE_LICENSE("GPL v2");