remarkable-linux/drivers/staging/greybus/hid.c
Greg Kroah-Hartman 863dbc52e7 staging: greybus: Remove redundant license text
Now that the SPDX tag is in all greybus files, that identifies the
license in a specific and legally-defined manner.  So the extra GPL text
wording can be removed as it is no longer needed at all.

This is done on a quest to remove the 700+ different ways that files in
the kernel describe the GPL license text.  And there's unneeded stuff
like the address (sometimes incorrect) for the FSF which is never
needed.

No copyright headers or other non-license-description text was removed.

Cc: Vaibhav Hiremath <hvaibhav.linux@gmail.com>
Reviewed-by: Alex Elder <elder@linaro.org>
Acked-by: Vaibhav Agarwal <vaibhav.sr@gmail.com>
Acked-by: David Lin <dtwlin@gmail.com>
Acked-by: Johan Hovold <johan@kernel.org>
Acked-by: Viresh Kumar <viresh.kumar@linaro.org>
Acked-by: Mark Greer <mgreer@animalcreek.com>
Acked-by: Rui Miguel Silva <rmfrfs@gmail.com>
Acked-by: "Bryan O'Donoghue" <pure.logic@nexus-software.ie>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2017-11-11 14:46:21 +01:00

521 lines
12 KiB
C

// SPDX-License-Identifier: GPL-2.0
/*
* HID class driver for the Greybus.
*
* Copyright 2014 Google Inc.
* Copyright 2014 Linaro Ltd.
*/
#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;
};
/* 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;
ret = gb_hid_set_power(ghid, GB_HID_TYPE_PWR_ON);
if (ret < 0)
return ret;
set_bit(GB_HID_STARTED, &ghid->flags);
return 0;
}
static void gb_hid_close(struct hid_device *hid)
{
struct gb_hid *ghid = hid->driver_data;
int ret;
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);
}
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");