/***************************************************************************
* Copyright (C) 2010-2012 by Bruno Prémont <bonbons@linux-vserver.org> *
* *
* Based on Logitech G13 driver (v0.4) *
* Copyright (C) 2009 by Rick L. Vinyard, Jr. <rvinyard@cs.nmsu.edu> *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, version 2 of the License. *
* *
* This driver is distributed in the hope that 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 software. If not see <http://www.gnu.org/licenses/>. *
***************************************************************************/
#include <linux/hid.h>
#include <linux/hid-debug.h>
#include <linux/input.h>
#include "hid-ids.h"
#include <linux/fb.h>
#include <linux/vmalloc.h>
#include <linux/completion.h>
#include <linux/uaccess.h>
#include <linux/module.h>
#include "hid-picolcd.h"
/* Input device
*
* The PicoLCD has an IR receiver header, a built-in keypad with 5 keys
* and header for 4x4 key matrix. The built-in keys are part of the matrix.
*/
static const unsigned short def_keymap[PICOLCD_KEYS] = {
KEY_RESERVED, /* none */
KEY_BACK, /* col 4 + row 1 */
KEY_HOMEPAGE, /* col 3 + row 1 */
KEY_RESERVED, /* col 2 + row 1 */
KEY_RESERVED, /* col 1 + row 1 */
KEY_SCROLLUP, /* col 4 + row 2 */
KEY_OK, /* col 3 + row 2 */
KEY_SCROLLDOWN, /* col 2 + row 2 */
KEY_RESERVED, /* col 1 + row 2 */
KEY_RESERVED, /* col 4 + row 3 */
KEY_RESERVED, /* col 3 + row 3 */
KEY_RESERVED, /* col 2 + row 3 */
KEY_RESERVED, /* col 1 + row 3 */
KEY_RESERVED, /* col 4 + row 4 */
KEY_RESERVED, /* col 3 + row 4 */
KEY_RESERVED, /* col 2 + row 4 */
KEY_RESERVED, /* col 1 + row 4 */
};
/* Find a given report */
struct hid_report *picolcd_report(int id, struct hid_device *hdev, int dir)
{
struct list_head *feature_report_list = &hdev->report_enum[dir].report_list;
struct hid_report *report = NULL;
list_for_each_entry(report, feature_report_list, list) {
if (report->id == id)
return report;
}
hid_warn(hdev, "No report with id 0x%x found\n", id);
return NULL;
}
/* Submit a report and wait for a reply from device - if device fades away
* or does not respond in time, return NULL */
struct picolcd_pending *picolcd_send_and_wait(struct hid_device *hdev,
int report_id, const u8 *raw_data, int size)
{
struct picolcd_data *data = hid_get_drvdata(hdev);
struct picolcd_pending *work;
struct hid_report *report = picolcd_out_report(report_id, hdev);
unsigned long flags;
int i, j, k;
if (!report || !data)
return NULL;
if (data->status & PICOLCD_FAILED)
return NULL;
work = kzalloc(sizeof(*work), GFP_KERNEL);
if (!work)
return NULL;
init_completion(&work->ready);
work->out_report = report;
work->in_report = NULL;
work->raw_size = 0;
mutex_lock(&data->mutex);
spin_lock_irqsave(&data->lock, flags);
for (i = k = 0; i < report->maxfield; i++)
for (j = 0; j < report->field[i]->report_count; j++) {
hid_set_field(report->field[i], j, k < size ? raw_data[k] : 0);
k++;
}
if (data->status & PICOLCD_FAILED) {
kfree(work);
work = NULL;
} else {
data->pending = work;
hid_hw_request(data->hdev, report, HID_REQ_SET_REPORT);
spin_unlock_irqrestore(&data->lock, flags);
wait_for_completion_interruptible_timeout(&work->ready, HZ*2);
spin_lock_irqsave(&data->lock, flags);
data->pending = NULL;
}
spin_unlock_irqrestore(&data->lock, flags);
mutex_unlock(&data->mutex);
return work;
}
/*
* input class device
*/
static int picolcd_raw_keypad(struct picolcd_data *data,
struct hid_report *report, u8 *raw_data, int size)
{
/*
* Keypad event
* First and second data bytes list currently pressed keys,
* 0x00 means no key and at most 2 keys may be pressed at same time
*/
int i, j;
/* determine newly pressed keys */
for (i = 0; i < size; i++) {
unsigned int key_code;
if (raw_data[i] == 0)
continue;
for (j = 0; j < sizeof(data->pressed_keys); j++)
if (data->pressed_keys[j] == raw_data[i])
goto key_already_down;
for (j = 0; j < sizeof(data->pressed_keys); j++)
if (data->pressed_keys[j] == 0) {
data->pressed_keys[j] = raw_data[i];
break;
}
input_event(data->input_keys, EV_MSC, MSC_SCAN, raw_data[i]);
if (raw_data[i] < PICOLCD_KEYS)
key_code = data->keycode[raw_data[i]];
else
key_code = KEY_UNKNOWN;
if (key_code != KEY_UNKNOWN) {
dbg_hid(PICOLCD_NAME " got key press for %u:%d",
raw_data[i], key_code);
input_report_key(data->input_keys, key_code, 1);
}
input_sync(data->input_keys);
key_already_down:
continue;
}
/* determine newly released keys */
for (j = 0; j < sizeof(data->pressed_keys); j++) {
unsigned int key_code;
if (data->pressed_keys[j] == 0)
continue;
for (i = 0; i < size; i++)
if (data->pressed_keys[j] == raw_data[i])
goto key_still_down;
input_event(data->input_keys, EV_MSC, MSC_SCAN, data->pressed_keys[j]);
if (data->pressed_keys[j] < PICOLCD_KEYS)
key_code = data->keycode[data->pressed_keys[j]];
else
key_code = KEY_UNKNOWN;
if (key_code != KEY_UNKNOWN) {
dbg_hid(PICOLCD_NAME " got key release for %u:%d",
data->pressed_keys[j], key_code);
input_report_key(data->input_keys, key_code, 0);
}
input_sync(data->input_keys);
data->pressed_keys[j] = 0;
key_still_down:
continue;
}
return 1;
}
static int picolcd_check_version(struct hid_device *hdev)
{
struct picolcd_data *data = hid_get_drvdata(hdev);
struct picolcd_pending *verinfo;
int ret = 0;
if (!data)
return -ENODEV;
verinfo = picolcd_send_and_wait(hdev, REPORT_VERSION, NULL, 0);
if (!verinfo) {
hid_err(hdev, "no version response from PicoLCD\n");
return -ENODEV;
}
if (verinfo->raw_size == 2) {
data->version[0] = verinfo->raw_data[1];
data->version[1] = verinfo->raw_data[0];
if (data->status & PICOLCD_BOOTLOADER) {
hid_info(hdev, "PicoLCD, bootloader version %d.%d\n",
verinfo->raw_data[1], verinfo->raw_data[0]);
} else {
hid_info(hdev, "PicoLCD, firmware version %d.%d\n",
verinfo->raw_data[1], verinfo->raw_data[0]);
}
} else {
hid_err(hdev, "confused, got unexpected version response from PicoLCD\n");
ret = -EINVAL;
}
kfree(verinfo);
return ret;
}
/*
* Reset our device and wait for answer to VERSION request
*/
int picolcd_reset(struct hid_device *hdev)
{
struct picolcd_data *data = hid_get_drvdata(hdev);
struct hid_report *report = picolcd_out_report(REPORT_RESET, hdev);
unsigned long flags;
int error;
if (!data || !report || report->maxfield != 1)
return -ENODEV;
spin_lock_irqsave(&data->lock, flags);
if (hdev->product == USB_DEVICE_ID_PICOLCD_BOOTLOADER)
data->status |= PICOLCD_BOOTLOADER;
/* perform the reset */
hid_set_field(report->field[0], 0, 1);
if (data->status & PICOLCD_FAILED) {
spin_unlock_irqrestore(&data->lock, flags);
return -ENODEV;
}
hid_hw_request(hdev, report, HID_REQ_SET_REPORT);
spin_unlock_irqrestore(&data->lock, flags);
error = picolcd_check_version(hdev);
if (error)
return error;
picolcd_resume_lcd(data);
picolcd_resume_backlight(data);
picolcd_fb_refresh(data);
picolcd_leds_set(data);
return 0;
}
/*
* The "operation_mode" sysfs attribute
*/
static ssize_t picolcd_operation_mode_show(struct
评论0