162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 262306a36Sopenharmony_ci/*************************************************************************** 362306a36Sopenharmony_ci * Copyright (C) 2010-2012 by Bruno Prémont <bonbons@linux-vserver.org> * 462306a36Sopenharmony_ci * * 562306a36Sopenharmony_ci * Based on Logitech G13 driver (v0.4) * 662306a36Sopenharmony_ci * Copyright (C) 2009 by Rick L. Vinyard, Jr. <rvinyard@cs.nmsu.edu> * 762306a36Sopenharmony_ci * * 862306a36Sopenharmony_ci ***************************************************************************/ 962306a36Sopenharmony_ci 1062306a36Sopenharmony_ci#include <linux/hid.h> 1162306a36Sopenharmony_ci#include <linux/hid-debug.h> 1262306a36Sopenharmony_ci#include <linux/input.h> 1362306a36Sopenharmony_ci#include "hid-ids.h" 1462306a36Sopenharmony_ci 1562306a36Sopenharmony_ci#include <linux/fb.h> 1662306a36Sopenharmony_ci#include <linux/vmalloc.h> 1762306a36Sopenharmony_ci 1862306a36Sopenharmony_ci#include <linux/completion.h> 1962306a36Sopenharmony_ci#include <linux/uaccess.h> 2062306a36Sopenharmony_ci#include <linux/module.h> 2162306a36Sopenharmony_ci#include <linux/string.h> 2262306a36Sopenharmony_ci 2362306a36Sopenharmony_ci#include "hid-picolcd.h" 2462306a36Sopenharmony_ci 2562306a36Sopenharmony_ci 2662306a36Sopenharmony_ci/* Input device 2762306a36Sopenharmony_ci * 2862306a36Sopenharmony_ci * The PicoLCD has an IR receiver header, a built-in keypad with 5 keys 2962306a36Sopenharmony_ci * and header for 4x4 key matrix. The built-in keys are part of the matrix. 3062306a36Sopenharmony_ci */ 3162306a36Sopenharmony_cistatic const unsigned short def_keymap[PICOLCD_KEYS] = { 3262306a36Sopenharmony_ci KEY_RESERVED, /* none */ 3362306a36Sopenharmony_ci KEY_BACK, /* col 4 + row 1 */ 3462306a36Sopenharmony_ci KEY_HOMEPAGE, /* col 3 + row 1 */ 3562306a36Sopenharmony_ci KEY_RESERVED, /* col 2 + row 1 */ 3662306a36Sopenharmony_ci KEY_RESERVED, /* col 1 + row 1 */ 3762306a36Sopenharmony_ci KEY_SCROLLUP, /* col 4 + row 2 */ 3862306a36Sopenharmony_ci KEY_OK, /* col 3 + row 2 */ 3962306a36Sopenharmony_ci KEY_SCROLLDOWN, /* col 2 + row 2 */ 4062306a36Sopenharmony_ci KEY_RESERVED, /* col 1 + row 2 */ 4162306a36Sopenharmony_ci KEY_RESERVED, /* col 4 + row 3 */ 4262306a36Sopenharmony_ci KEY_RESERVED, /* col 3 + row 3 */ 4362306a36Sopenharmony_ci KEY_RESERVED, /* col 2 + row 3 */ 4462306a36Sopenharmony_ci KEY_RESERVED, /* col 1 + row 3 */ 4562306a36Sopenharmony_ci KEY_RESERVED, /* col 4 + row 4 */ 4662306a36Sopenharmony_ci KEY_RESERVED, /* col 3 + row 4 */ 4762306a36Sopenharmony_ci KEY_RESERVED, /* col 2 + row 4 */ 4862306a36Sopenharmony_ci KEY_RESERVED, /* col 1 + row 4 */ 4962306a36Sopenharmony_ci}; 5062306a36Sopenharmony_ci 5162306a36Sopenharmony_ci 5262306a36Sopenharmony_ci/* Find a given report */ 5362306a36Sopenharmony_cistruct hid_report *picolcd_report(int id, struct hid_device *hdev, int dir) 5462306a36Sopenharmony_ci{ 5562306a36Sopenharmony_ci struct list_head *feature_report_list = &hdev->report_enum[dir].report_list; 5662306a36Sopenharmony_ci struct hid_report *report = NULL; 5762306a36Sopenharmony_ci 5862306a36Sopenharmony_ci list_for_each_entry(report, feature_report_list, list) { 5962306a36Sopenharmony_ci if (report->id == id) 6062306a36Sopenharmony_ci return report; 6162306a36Sopenharmony_ci } 6262306a36Sopenharmony_ci hid_warn(hdev, "No report with id 0x%x found\n", id); 6362306a36Sopenharmony_ci return NULL; 6462306a36Sopenharmony_ci} 6562306a36Sopenharmony_ci 6662306a36Sopenharmony_ci/* Submit a report and wait for a reply from device - if device fades away 6762306a36Sopenharmony_ci * or does not respond in time, return NULL */ 6862306a36Sopenharmony_cistruct picolcd_pending *picolcd_send_and_wait(struct hid_device *hdev, 6962306a36Sopenharmony_ci int report_id, const u8 *raw_data, int size) 7062306a36Sopenharmony_ci{ 7162306a36Sopenharmony_ci struct picolcd_data *data = hid_get_drvdata(hdev); 7262306a36Sopenharmony_ci struct picolcd_pending *work; 7362306a36Sopenharmony_ci struct hid_report *report = picolcd_out_report(report_id, hdev); 7462306a36Sopenharmony_ci unsigned long flags; 7562306a36Sopenharmony_ci int i, j, k; 7662306a36Sopenharmony_ci 7762306a36Sopenharmony_ci if (!report || !data) 7862306a36Sopenharmony_ci return NULL; 7962306a36Sopenharmony_ci if (data->status & PICOLCD_FAILED) 8062306a36Sopenharmony_ci return NULL; 8162306a36Sopenharmony_ci work = kzalloc(sizeof(*work), GFP_KERNEL); 8262306a36Sopenharmony_ci if (!work) 8362306a36Sopenharmony_ci return NULL; 8462306a36Sopenharmony_ci 8562306a36Sopenharmony_ci init_completion(&work->ready); 8662306a36Sopenharmony_ci work->out_report = report; 8762306a36Sopenharmony_ci work->in_report = NULL; 8862306a36Sopenharmony_ci work->raw_size = 0; 8962306a36Sopenharmony_ci 9062306a36Sopenharmony_ci mutex_lock(&data->mutex); 9162306a36Sopenharmony_ci spin_lock_irqsave(&data->lock, flags); 9262306a36Sopenharmony_ci for (i = k = 0; i < report->maxfield; i++) 9362306a36Sopenharmony_ci for (j = 0; j < report->field[i]->report_count; j++) { 9462306a36Sopenharmony_ci hid_set_field(report->field[i], j, k < size ? raw_data[k] : 0); 9562306a36Sopenharmony_ci k++; 9662306a36Sopenharmony_ci } 9762306a36Sopenharmony_ci if (data->status & PICOLCD_FAILED) { 9862306a36Sopenharmony_ci kfree(work); 9962306a36Sopenharmony_ci work = NULL; 10062306a36Sopenharmony_ci } else { 10162306a36Sopenharmony_ci data->pending = work; 10262306a36Sopenharmony_ci hid_hw_request(data->hdev, report, HID_REQ_SET_REPORT); 10362306a36Sopenharmony_ci spin_unlock_irqrestore(&data->lock, flags); 10462306a36Sopenharmony_ci wait_for_completion_interruptible_timeout(&work->ready, HZ*2); 10562306a36Sopenharmony_ci spin_lock_irqsave(&data->lock, flags); 10662306a36Sopenharmony_ci data->pending = NULL; 10762306a36Sopenharmony_ci } 10862306a36Sopenharmony_ci spin_unlock_irqrestore(&data->lock, flags); 10962306a36Sopenharmony_ci mutex_unlock(&data->mutex); 11062306a36Sopenharmony_ci return work; 11162306a36Sopenharmony_ci} 11262306a36Sopenharmony_ci 11362306a36Sopenharmony_ci/* 11462306a36Sopenharmony_ci * input class device 11562306a36Sopenharmony_ci */ 11662306a36Sopenharmony_cistatic int picolcd_raw_keypad(struct picolcd_data *data, 11762306a36Sopenharmony_ci struct hid_report *report, u8 *raw_data, int size) 11862306a36Sopenharmony_ci{ 11962306a36Sopenharmony_ci /* 12062306a36Sopenharmony_ci * Keypad event 12162306a36Sopenharmony_ci * First and second data bytes list currently pressed keys, 12262306a36Sopenharmony_ci * 0x00 means no key and at most 2 keys may be pressed at same time 12362306a36Sopenharmony_ci */ 12462306a36Sopenharmony_ci int i, j; 12562306a36Sopenharmony_ci 12662306a36Sopenharmony_ci /* determine newly pressed keys */ 12762306a36Sopenharmony_ci for (i = 0; i < size; i++) { 12862306a36Sopenharmony_ci unsigned int key_code; 12962306a36Sopenharmony_ci if (raw_data[i] == 0) 13062306a36Sopenharmony_ci continue; 13162306a36Sopenharmony_ci for (j = 0; j < sizeof(data->pressed_keys); j++) 13262306a36Sopenharmony_ci if (data->pressed_keys[j] == raw_data[i]) 13362306a36Sopenharmony_ci goto key_already_down; 13462306a36Sopenharmony_ci for (j = 0; j < sizeof(data->pressed_keys); j++) 13562306a36Sopenharmony_ci if (data->pressed_keys[j] == 0) { 13662306a36Sopenharmony_ci data->pressed_keys[j] = raw_data[i]; 13762306a36Sopenharmony_ci break; 13862306a36Sopenharmony_ci } 13962306a36Sopenharmony_ci input_event(data->input_keys, EV_MSC, MSC_SCAN, raw_data[i]); 14062306a36Sopenharmony_ci if (raw_data[i] < PICOLCD_KEYS) 14162306a36Sopenharmony_ci key_code = data->keycode[raw_data[i]]; 14262306a36Sopenharmony_ci else 14362306a36Sopenharmony_ci key_code = KEY_UNKNOWN; 14462306a36Sopenharmony_ci if (key_code != KEY_UNKNOWN) { 14562306a36Sopenharmony_ci dbg_hid(PICOLCD_NAME " got key press for %u:%d", 14662306a36Sopenharmony_ci raw_data[i], key_code); 14762306a36Sopenharmony_ci input_report_key(data->input_keys, key_code, 1); 14862306a36Sopenharmony_ci } 14962306a36Sopenharmony_ci input_sync(data->input_keys); 15062306a36Sopenharmony_cikey_already_down: 15162306a36Sopenharmony_ci continue; 15262306a36Sopenharmony_ci } 15362306a36Sopenharmony_ci 15462306a36Sopenharmony_ci /* determine newly released keys */ 15562306a36Sopenharmony_ci for (j = 0; j < sizeof(data->pressed_keys); j++) { 15662306a36Sopenharmony_ci unsigned int key_code; 15762306a36Sopenharmony_ci if (data->pressed_keys[j] == 0) 15862306a36Sopenharmony_ci continue; 15962306a36Sopenharmony_ci for (i = 0; i < size; i++) 16062306a36Sopenharmony_ci if (data->pressed_keys[j] == raw_data[i]) 16162306a36Sopenharmony_ci goto key_still_down; 16262306a36Sopenharmony_ci input_event(data->input_keys, EV_MSC, MSC_SCAN, data->pressed_keys[j]); 16362306a36Sopenharmony_ci if (data->pressed_keys[j] < PICOLCD_KEYS) 16462306a36Sopenharmony_ci key_code = data->keycode[data->pressed_keys[j]]; 16562306a36Sopenharmony_ci else 16662306a36Sopenharmony_ci key_code = KEY_UNKNOWN; 16762306a36Sopenharmony_ci if (key_code != KEY_UNKNOWN) { 16862306a36Sopenharmony_ci dbg_hid(PICOLCD_NAME " got key release for %u:%d", 16962306a36Sopenharmony_ci data->pressed_keys[j], key_code); 17062306a36Sopenharmony_ci input_report_key(data->input_keys, key_code, 0); 17162306a36Sopenharmony_ci } 17262306a36Sopenharmony_ci input_sync(data->input_keys); 17362306a36Sopenharmony_ci data->pressed_keys[j] = 0; 17462306a36Sopenharmony_cikey_still_down: 17562306a36Sopenharmony_ci continue; 17662306a36Sopenharmony_ci } 17762306a36Sopenharmony_ci return 1; 17862306a36Sopenharmony_ci} 17962306a36Sopenharmony_ci 18062306a36Sopenharmony_cistatic int picolcd_check_version(struct hid_device *hdev) 18162306a36Sopenharmony_ci{ 18262306a36Sopenharmony_ci struct picolcd_data *data = hid_get_drvdata(hdev); 18362306a36Sopenharmony_ci struct picolcd_pending *verinfo; 18462306a36Sopenharmony_ci int ret = 0; 18562306a36Sopenharmony_ci 18662306a36Sopenharmony_ci if (!data) 18762306a36Sopenharmony_ci return -ENODEV; 18862306a36Sopenharmony_ci 18962306a36Sopenharmony_ci verinfo = picolcd_send_and_wait(hdev, REPORT_VERSION, NULL, 0); 19062306a36Sopenharmony_ci if (!verinfo) { 19162306a36Sopenharmony_ci hid_err(hdev, "no version response from PicoLCD\n"); 19262306a36Sopenharmony_ci return -ENODEV; 19362306a36Sopenharmony_ci } 19462306a36Sopenharmony_ci 19562306a36Sopenharmony_ci if (verinfo->raw_size == 2) { 19662306a36Sopenharmony_ci data->version[0] = verinfo->raw_data[1]; 19762306a36Sopenharmony_ci data->version[1] = verinfo->raw_data[0]; 19862306a36Sopenharmony_ci if (data->status & PICOLCD_BOOTLOADER) { 19962306a36Sopenharmony_ci hid_info(hdev, "PicoLCD, bootloader version %d.%d\n", 20062306a36Sopenharmony_ci verinfo->raw_data[1], verinfo->raw_data[0]); 20162306a36Sopenharmony_ci } else { 20262306a36Sopenharmony_ci hid_info(hdev, "PicoLCD, firmware version %d.%d\n", 20362306a36Sopenharmony_ci verinfo->raw_data[1], verinfo->raw_data[0]); 20462306a36Sopenharmony_ci } 20562306a36Sopenharmony_ci } else { 20662306a36Sopenharmony_ci hid_err(hdev, "confused, got unexpected version response from PicoLCD\n"); 20762306a36Sopenharmony_ci ret = -EINVAL; 20862306a36Sopenharmony_ci } 20962306a36Sopenharmony_ci kfree(verinfo); 21062306a36Sopenharmony_ci return ret; 21162306a36Sopenharmony_ci} 21262306a36Sopenharmony_ci 21362306a36Sopenharmony_ci/* 21462306a36Sopenharmony_ci * Reset our device and wait for answer to VERSION request 21562306a36Sopenharmony_ci */ 21662306a36Sopenharmony_ciint picolcd_reset(struct hid_device *hdev) 21762306a36Sopenharmony_ci{ 21862306a36Sopenharmony_ci struct picolcd_data *data = hid_get_drvdata(hdev); 21962306a36Sopenharmony_ci struct hid_report *report = picolcd_out_report(REPORT_RESET, hdev); 22062306a36Sopenharmony_ci unsigned long flags; 22162306a36Sopenharmony_ci int error; 22262306a36Sopenharmony_ci 22362306a36Sopenharmony_ci if (!data || !report || report->maxfield != 1) 22462306a36Sopenharmony_ci return -ENODEV; 22562306a36Sopenharmony_ci 22662306a36Sopenharmony_ci spin_lock_irqsave(&data->lock, flags); 22762306a36Sopenharmony_ci if (hdev->product == USB_DEVICE_ID_PICOLCD_BOOTLOADER) 22862306a36Sopenharmony_ci data->status |= PICOLCD_BOOTLOADER; 22962306a36Sopenharmony_ci 23062306a36Sopenharmony_ci /* perform the reset */ 23162306a36Sopenharmony_ci hid_set_field(report->field[0], 0, 1); 23262306a36Sopenharmony_ci if (data->status & PICOLCD_FAILED) { 23362306a36Sopenharmony_ci spin_unlock_irqrestore(&data->lock, flags); 23462306a36Sopenharmony_ci return -ENODEV; 23562306a36Sopenharmony_ci } 23662306a36Sopenharmony_ci hid_hw_request(hdev, report, HID_REQ_SET_REPORT); 23762306a36Sopenharmony_ci spin_unlock_irqrestore(&data->lock, flags); 23862306a36Sopenharmony_ci 23962306a36Sopenharmony_ci error = picolcd_check_version(hdev); 24062306a36Sopenharmony_ci if (error) 24162306a36Sopenharmony_ci return error; 24262306a36Sopenharmony_ci 24362306a36Sopenharmony_ci picolcd_resume_lcd(data); 24462306a36Sopenharmony_ci picolcd_resume_backlight(data); 24562306a36Sopenharmony_ci picolcd_fb_refresh(data); 24662306a36Sopenharmony_ci picolcd_leds_set(data); 24762306a36Sopenharmony_ci return 0; 24862306a36Sopenharmony_ci} 24962306a36Sopenharmony_ci 25062306a36Sopenharmony_ci/* 25162306a36Sopenharmony_ci * The "operation_mode" sysfs attribute 25262306a36Sopenharmony_ci */ 25362306a36Sopenharmony_cistatic ssize_t picolcd_operation_mode_show(struct device *dev, 25462306a36Sopenharmony_ci struct device_attribute *attr, char *buf) 25562306a36Sopenharmony_ci{ 25662306a36Sopenharmony_ci struct picolcd_data *data = dev_get_drvdata(dev); 25762306a36Sopenharmony_ci 25862306a36Sopenharmony_ci if (data->status & PICOLCD_BOOTLOADER) 25962306a36Sopenharmony_ci return snprintf(buf, PAGE_SIZE, "[bootloader] lcd\n"); 26062306a36Sopenharmony_ci else 26162306a36Sopenharmony_ci return snprintf(buf, PAGE_SIZE, "bootloader [lcd]\n"); 26262306a36Sopenharmony_ci} 26362306a36Sopenharmony_ci 26462306a36Sopenharmony_cistatic ssize_t picolcd_operation_mode_store(struct device *dev, 26562306a36Sopenharmony_ci struct device_attribute *attr, const char *buf, size_t count) 26662306a36Sopenharmony_ci{ 26762306a36Sopenharmony_ci struct picolcd_data *data = dev_get_drvdata(dev); 26862306a36Sopenharmony_ci struct hid_report *report = NULL; 26962306a36Sopenharmony_ci int timeout = data->opmode_delay; 27062306a36Sopenharmony_ci unsigned long flags; 27162306a36Sopenharmony_ci 27262306a36Sopenharmony_ci if (sysfs_streq(buf, "lcd")) { 27362306a36Sopenharmony_ci if (data->status & PICOLCD_BOOTLOADER) 27462306a36Sopenharmony_ci report = picolcd_out_report(REPORT_EXIT_FLASHER, data->hdev); 27562306a36Sopenharmony_ci } else if (sysfs_streq(buf, "bootloader")) { 27662306a36Sopenharmony_ci if (!(data->status & PICOLCD_BOOTLOADER)) 27762306a36Sopenharmony_ci report = picolcd_out_report(REPORT_EXIT_KEYBOARD, data->hdev); 27862306a36Sopenharmony_ci } else { 27962306a36Sopenharmony_ci return -EINVAL; 28062306a36Sopenharmony_ci } 28162306a36Sopenharmony_ci 28262306a36Sopenharmony_ci if (!report || report->maxfield != 1) 28362306a36Sopenharmony_ci return -EINVAL; 28462306a36Sopenharmony_ci 28562306a36Sopenharmony_ci spin_lock_irqsave(&data->lock, flags); 28662306a36Sopenharmony_ci hid_set_field(report->field[0], 0, timeout & 0xff); 28762306a36Sopenharmony_ci hid_set_field(report->field[0], 1, (timeout >> 8) & 0xff); 28862306a36Sopenharmony_ci hid_hw_request(data->hdev, report, HID_REQ_SET_REPORT); 28962306a36Sopenharmony_ci spin_unlock_irqrestore(&data->lock, flags); 29062306a36Sopenharmony_ci return count; 29162306a36Sopenharmony_ci} 29262306a36Sopenharmony_ci 29362306a36Sopenharmony_cistatic DEVICE_ATTR(operation_mode, 0644, picolcd_operation_mode_show, 29462306a36Sopenharmony_ci picolcd_operation_mode_store); 29562306a36Sopenharmony_ci 29662306a36Sopenharmony_ci/* 29762306a36Sopenharmony_ci * The "operation_mode_delay" sysfs attribute 29862306a36Sopenharmony_ci */ 29962306a36Sopenharmony_cistatic ssize_t picolcd_operation_mode_delay_show(struct device *dev, 30062306a36Sopenharmony_ci struct device_attribute *attr, char *buf) 30162306a36Sopenharmony_ci{ 30262306a36Sopenharmony_ci struct picolcd_data *data = dev_get_drvdata(dev); 30362306a36Sopenharmony_ci 30462306a36Sopenharmony_ci return snprintf(buf, PAGE_SIZE, "%hu\n", data->opmode_delay); 30562306a36Sopenharmony_ci} 30662306a36Sopenharmony_ci 30762306a36Sopenharmony_cistatic ssize_t picolcd_operation_mode_delay_store(struct device *dev, 30862306a36Sopenharmony_ci struct device_attribute *attr, const char *buf, size_t count) 30962306a36Sopenharmony_ci{ 31062306a36Sopenharmony_ci struct picolcd_data *data = dev_get_drvdata(dev); 31162306a36Sopenharmony_ci unsigned u; 31262306a36Sopenharmony_ci if (sscanf(buf, "%u", &u) != 1) 31362306a36Sopenharmony_ci return -EINVAL; 31462306a36Sopenharmony_ci if (u > 30000) 31562306a36Sopenharmony_ci return -EINVAL; 31662306a36Sopenharmony_ci else 31762306a36Sopenharmony_ci data->opmode_delay = u; 31862306a36Sopenharmony_ci return count; 31962306a36Sopenharmony_ci} 32062306a36Sopenharmony_ci 32162306a36Sopenharmony_cistatic DEVICE_ATTR(operation_mode_delay, 0644, picolcd_operation_mode_delay_show, 32262306a36Sopenharmony_ci picolcd_operation_mode_delay_store); 32362306a36Sopenharmony_ci 32462306a36Sopenharmony_ci/* 32562306a36Sopenharmony_ci * Handle raw report as sent by device 32662306a36Sopenharmony_ci */ 32762306a36Sopenharmony_cistatic int picolcd_raw_event(struct hid_device *hdev, 32862306a36Sopenharmony_ci struct hid_report *report, u8 *raw_data, int size) 32962306a36Sopenharmony_ci{ 33062306a36Sopenharmony_ci struct picolcd_data *data = hid_get_drvdata(hdev); 33162306a36Sopenharmony_ci unsigned long flags; 33262306a36Sopenharmony_ci 33362306a36Sopenharmony_ci if (!data) 33462306a36Sopenharmony_ci return 1; 33562306a36Sopenharmony_ci 33662306a36Sopenharmony_ci if (size > 64) { 33762306a36Sopenharmony_ci hid_warn(hdev, "invalid size value (%d) for picolcd raw event (%d)\n", 33862306a36Sopenharmony_ci size, report->id); 33962306a36Sopenharmony_ci return 0; 34062306a36Sopenharmony_ci } 34162306a36Sopenharmony_ci 34262306a36Sopenharmony_ci if (report->id == REPORT_KEY_STATE) { 34362306a36Sopenharmony_ci if (data->input_keys) 34462306a36Sopenharmony_ci picolcd_raw_keypad(data, report, raw_data+1, size-1); 34562306a36Sopenharmony_ci } else if (report->id == REPORT_IR_DATA) { 34662306a36Sopenharmony_ci picolcd_raw_cir(data, report, raw_data+1, size-1); 34762306a36Sopenharmony_ci } else { 34862306a36Sopenharmony_ci spin_lock_irqsave(&data->lock, flags); 34962306a36Sopenharmony_ci /* 35062306a36Sopenharmony_ci * We let the caller of picolcd_send_and_wait() check if the 35162306a36Sopenharmony_ci * report we got is one of the expected ones or not. 35262306a36Sopenharmony_ci */ 35362306a36Sopenharmony_ci if (data->pending) { 35462306a36Sopenharmony_ci memcpy(data->pending->raw_data, raw_data+1, size-1); 35562306a36Sopenharmony_ci data->pending->raw_size = size-1; 35662306a36Sopenharmony_ci data->pending->in_report = report; 35762306a36Sopenharmony_ci complete(&data->pending->ready); 35862306a36Sopenharmony_ci } 35962306a36Sopenharmony_ci spin_unlock_irqrestore(&data->lock, flags); 36062306a36Sopenharmony_ci } 36162306a36Sopenharmony_ci 36262306a36Sopenharmony_ci picolcd_debug_raw_event(data, hdev, report, raw_data, size); 36362306a36Sopenharmony_ci return 1; 36462306a36Sopenharmony_ci} 36562306a36Sopenharmony_ci 36662306a36Sopenharmony_ci#ifdef CONFIG_PM 36762306a36Sopenharmony_cistatic int picolcd_suspend(struct hid_device *hdev, pm_message_t message) 36862306a36Sopenharmony_ci{ 36962306a36Sopenharmony_ci if (PMSG_IS_AUTO(message)) 37062306a36Sopenharmony_ci return 0; 37162306a36Sopenharmony_ci 37262306a36Sopenharmony_ci picolcd_suspend_backlight(hid_get_drvdata(hdev)); 37362306a36Sopenharmony_ci dbg_hid(PICOLCD_NAME " device ready for suspend\n"); 37462306a36Sopenharmony_ci return 0; 37562306a36Sopenharmony_ci} 37662306a36Sopenharmony_ci 37762306a36Sopenharmony_cistatic int picolcd_resume(struct hid_device *hdev) 37862306a36Sopenharmony_ci{ 37962306a36Sopenharmony_ci int ret; 38062306a36Sopenharmony_ci ret = picolcd_resume_backlight(hid_get_drvdata(hdev)); 38162306a36Sopenharmony_ci if (ret) 38262306a36Sopenharmony_ci dbg_hid(PICOLCD_NAME " restoring backlight failed: %d\n", ret); 38362306a36Sopenharmony_ci return 0; 38462306a36Sopenharmony_ci} 38562306a36Sopenharmony_ci 38662306a36Sopenharmony_cistatic int picolcd_reset_resume(struct hid_device *hdev) 38762306a36Sopenharmony_ci{ 38862306a36Sopenharmony_ci int ret; 38962306a36Sopenharmony_ci ret = picolcd_reset(hdev); 39062306a36Sopenharmony_ci if (ret) 39162306a36Sopenharmony_ci dbg_hid(PICOLCD_NAME " resetting our device failed: %d\n", ret); 39262306a36Sopenharmony_ci ret = picolcd_fb_reset(hid_get_drvdata(hdev), 0); 39362306a36Sopenharmony_ci if (ret) 39462306a36Sopenharmony_ci dbg_hid(PICOLCD_NAME " restoring framebuffer content failed: %d\n", ret); 39562306a36Sopenharmony_ci ret = picolcd_resume_lcd(hid_get_drvdata(hdev)); 39662306a36Sopenharmony_ci if (ret) 39762306a36Sopenharmony_ci dbg_hid(PICOLCD_NAME " restoring lcd failed: %d\n", ret); 39862306a36Sopenharmony_ci ret = picolcd_resume_backlight(hid_get_drvdata(hdev)); 39962306a36Sopenharmony_ci if (ret) 40062306a36Sopenharmony_ci dbg_hid(PICOLCD_NAME " restoring backlight failed: %d\n", ret); 40162306a36Sopenharmony_ci picolcd_leds_set(hid_get_drvdata(hdev)); 40262306a36Sopenharmony_ci return 0; 40362306a36Sopenharmony_ci} 40462306a36Sopenharmony_ci#endif 40562306a36Sopenharmony_ci 40662306a36Sopenharmony_ci/* initialize keypad input device */ 40762306a36Sopenharmony_cistatic int picolcd_init_keys(struct picolcd_data *data, 40862306a36Sopenharmony_ci struct hid_report *report) 40962306a36Sopenharmony_ci{ 41062306a36Sopenharmony_ci struct hid_device *hdev = data->hdev; 41162306a36Sopenharmony_ci struct input_dev *idev; 41262306a36Sopenharmony_ci int error, i; 41362306a36Sopenharmony_ci 41462306a36Sopenharmony_ci if (!report) 41562306a36Sopenharmony_ci return -ENODEV; 41662306a36Sopenharmony_ci if (report->maxfield != 1 || report->field[0]->report_count != 2 || 41762306a36Sopenharmony_ci report->field[0]->report_size != 8) { 41862306a36Sopenharmony_ci hid_err(hdev, "unsupported KEY_STATE report\n"); 41962306a36Sopenharmony_ci return -EINVAL; 42062306a36Sopenharmony_ci } 42162306a36Sopenharmony_ci 42262306a36Sopenharmony_ci idev = input_allocate_device(); 42362306a36Sopenharmony_ci if (idev == NULL) { 42462306a36Sopenharmony_ci hid_err(hdev, "failed to allocate input device\n"); 42562306a36Sopenharmony_ci return -ENOMEM; 42662306a36Sopenharmony_ci } 42762306a36Sopenharmony_ci input_set_drvdata(idev, hdev); 42862306a36Sopenharmony_ci memcpy(data->keycode, def_keymap, sizeof(def_keymap)); 42962306a36Sopenharmony_ci idev->name = hdev->name; 43062306a36Sopenharmony_ci idev->phys = hdev->phys; 43162306a36Sopenharmony_ci idev->uniq = hdev->uniq; 43262306a36Sopenharmony_ci idev->id.bustype = hdev->bus; 43362306a36Sopenharmony_ci idev->id.vendor = hdev->vendor; 43462306a36Sopenharmony_ci idev->id.product = hdev->product; 43562306a36Sopenharmony_ci idev->id.version = hdev->version; 43662306a36Sopenharmony_ci idev->dev.parent = &hdev->dev; 43762306a36Sopenharmony_ci idev->keycode = &data->keycode; 43862306a36Sopenharmony_ci idev->keycodemax = PICOLCD_KEYS; 43962306a36Sopenharmony_ci idev->keycodesize = sizeof(data->keycode[0]); 44062306a36Sopenharmony_ci input_set_capability(idev, EV_MSC, MSC_SCAN); 44162306a36Sopenharmony_ci set_bit(EV_REP, idev->evbit); 44262306a36Sopenharmony_ci for (i = 0; i < PICOLCD_KEYS; i++) 44362306a36Sopenharmony_ci input_set_capability(idev, EV_KEY, data->keycode[i]); 44462306a36Sopenharmony_ci error = input_register_device(idev); 44562306a36Sopenharmony_ci if (error) { 44662306a36Sopenharmony_ci hid_err(hdev, "error registering the input device\n"); 44762306a36Sopenharmony_ci input_free_device(idev); 44862306a36Sopenharmony_ci return error; 44962306a36Sopenharmony_ci } 45062306a36Sopenharmony_ci data->input_keys = idev; 45162306a36Sopenharmony_ci return 0; 45262306a36Sopenharmony_ci} 45362306a36Sopenharmony_ci 45462306a36Sopenharmony_cistatic void picolcd_exit_keys(struct picolcd_data *data) 45562306a36Sopenharmony_ci{ 45662306a36Sopenharmony_ci struct input_dev *idev = data->input_keys; 45762306a36Sopenharmony_ci 45862306a36Sopenharmony_ci data->input_keys = NULL; 45962306a36Sopenharmony_ci if (idev) 46062306a36Sopenharmony_ci input_unregister_device(idev); 46162306a36Sopenharmony_ci} 46262306a36Sopenharmony_ci 46362306a36Sopenharmony_cistatic int picolcd_probe_lcd(struct hid_device *hdev, struct picolcd_data *data) 46462306a36Sopenharmony_ci{ 46562306a36Sopenharmony_ci int error; 46662306a36Sopenharmony_ci 46762306a36Sopenharmony_ci /* Setup keypad input device */ 46862306a36Sopenharmony_ci error = picolcd_init_keys(data, picolcd_in_report(REPORT_KEY_STATE, hdev)); 46962306a36Sopenharmony_ci if (error) 47062306a36Sopenharmony_ci goto err; 47162306a36Sopenharmony_ci 47262306a36Sopenharmony_ci /* Setup CIR input device */ 47362306a36Sopenharmony_ci error = picolcd_init_cir(data, picolcd_in_report(REPORT_IR_DATA, hdev)); 47462306a36Sopenharmony_ci if (error) 47562306a36Sopenharmony_ci goto err; 47662306a36Sopenharmony_ci 47762306a36Sopenharmony_ci /* Set up the framebuffer device */ 47862306a36Sopenharmony_ci error = picolcd_init_framebuffer(data); 47962306a36Sopenharmony_ci if (error) 48062306a36Sopenharmony_ci goto err; 48162306a36Sopenharmony_ci 48262306a36Sopenharmony_ci /* Setup lcd class device */ 48362306a36Sopenharmony_ci error = picolcd_init_lcd(data, picolcd_out_report(REPORT_CONTRAST, hdev)); 48462306a36Sopenharmony_ci if (error) 48562306a36Sopenharmony_ci goto err; 48662306a36Sopenharmony_ci 48762306a36Sopenharmony_ci /* Setup backlight class device */ 48862306a36Sopenharmony_ci error = picolcd_init_backlight(data, picolcd_out_report(REPORT_BRIGHTNESS, hdev)); 48962306a36Sopenharmony_ci if (error) 49062306a36Sopenharmony_ci goto err; 49162306a36Sopenharmony_ci 49262306a36Sopenharmony_ci /* Setup the LED class devices */ 49362306a36Sopenharmony_ci error = picolcd_init_leds(data, picolcd_out_report(REPORT_LED_STATE, hdev)); 49462306a36Sopenharmony_ci if (error) 49562306a36Sopenharmony_ci goto err; 49662306a36Sopenharmony_ci 49762306a36Sopenharmony_ci picolcd_init_devfs(data, picolcd_out_report(REPORT_EE_READ, hdev), 49862306a36Sopenharmony_ci picolcd_out_report(REPORT_EE_WRITE, hdev), 49962306a36Sopenharmony_ci picolcd_out_report(REPORT_READ_MEMORY, hdev), 50062306a36Sopenharmony_ci picolcd_out_report(REPORT_WRITE_MEMORY, hdev), 50162306a36Sopenharmony_ci picolcd_out_report(REPORT_RESET, hdev)); 50262306a36Sopenharmony_ci return 0; 50362306a36Sopenharmony_cierr: 50462306a36Sopenharmony_ci picolcd_exit_leds(data); 50562306a36Sopenharmony_ci picolcd_exit_backlight(data); 50662306a36Sopenharmony_ci picolcd_exit_lcd(data); 50762306a36Sopenharmony_ci picolcd_exit_framebuffer(data); 50862306a36Sopenharmony_ci picolcd_exit_cir(data); 50962306a36Sopenharmony_ci picolcd_exit_keys(data); 51062306a36Sopenharmony_ci return error; 51162306a36Sopenharmony_ci} 51262306a36Sopenharmony_ci 51362306a36Sopenharmony_cistatic int picolcd_probe_bootloader(struct hid_device *hdev, struct picolcd_data *data) 51462306a36Sopenharmony_ci{ 51562306a36Sopenharmony_ci picolcd_init_devfs(data, NULL, NULL, 51662306a36Sopenharmony_ci picolcd_out_report(REPORT_BL_READ_MEMORY, hdev), 51762306a36Sopenharmony_ci picolcd_out_report(REPORT_BL_WRITE_MEMORY, hdev), NULL); 51862306a36Sopenharmony_ci return 0; 51962306a36Sopenharmony_ci} 52062306a36Sopenharmony_ci 52162306a36Sopenharmony_cistatic int picolcd_probe(struct hid_device *hdev, 52262306a36Sopenharmony_ci const struct hid_device_id *id) 52362306a36Sopenharmony_ci{ 52462306a36Sopenharmony_ci struct picolcd_data *data; 52562306a36Sopenharmony_ci int error = -ENOMEM; 52662306a36Sopenharmony_ci 52762306a36Sopenharmony_ci dbg_hid(PICOLCD_NAME " hardware probe...\n"); 52862306a36Sopenharmony_ci 52962306a36Sopenharmony_ci /* 53062306a36Sopenharmony_ci * Let's allocate the picolcd data structure, set some reasonable 53162306a36Sopenharmony_ci * defaults, and associate it with the device 53262306a36Sopenharmony_ci */ 53362306a36Sopenharmony_ci data = kzalloc(sizeof(struct picolcd_data), GFP_KERNEL); 53462306a36Sopenharmony_ci if (data == NULL) { 53562306a36Sopenharmony_ci hid_err(hdev, "can't allocate space for Minibox PicoLCD device data\n"); 53662306a36Sopenharmony_ci return -ENOMEM; 53762306a36Sopenharmony_ci } 53862306a36Sopenharmony_ci 53962306a36Sopenharmony_ci spin_lock_init(&data->lock); 54062306a36Sopenharmony_ci mutex_init(&data->mutex); 54162306a36Sopenharmony_ci data->hdev = hdev; 54262306a36Sopenharmony_ci data->opmode_delay = 5000; 54362306a36Sopenharmony_ci if (hdev->product == USB_DEVICE_ID_PICOLCD_BOOTLOADER) 54462306a36Sopenharmony_ci data->status |= PICOLCD_BOOTLOADER; 54562306a36Sopenharmony_ci hid_set_drvdata(hdev, data); 54662306a36Sopenharmony_ci 54762306a36Sopenharmony_ci /* Parse the device reports and start it up */ 54862306a36Sopenharmony_ci error = hid_parse(hdev); 54962306a36Sopenharmony_ci if (error) { 55062306a36Sopenharmony_ci hid_err(hdev, "device report parse failed\n"); 55162306a36Sopenharmony_ci goto err_cleanup_data; 55262306a36Sopenharmony_ci } 55362306a36Sopenharmony_ci 55462306a36Sopenharmony_ci error = hid_hw_start(hdev, 0); 55562306a36Sopenharmony_ci if (error) { 55662306a36Sopenharmony_ci hid_err(hdev, "hardware start failed\n"); 55762306a36Sopenharmony_ci goto err_cleanup_data; 55862306a36Sopenharmony_ci } 55962306a36Sopenharmony_ci 56062306a36Sopenharmony_ci error = hid_hw_open(hdev); 56162306a36Sopenharmony_ci if (error) { 56262306a36Sopenharmony_ci hid_err(hdev, "failed to open input interrupt pipe for key and IR events\n"); 56362306a36Sopenharmony_ci goto err_cleanup_hid_hw; 56462306a36Sopenharmony_ci } 56562306a36Sopenharmony_ci 56662306a36Sopenharmony_ci error = device_create_file(&hdev->dev, &dev_attr_operation_mode_delay); 56762306a36Sopenharmony_ci if (error) { 56862306a36Sopenharmony_ci hid_err(hdev, "failed to create sysfs attributes\n"); 56962306a36Sopenharmony_ci goto err_cleanup_hid_ll; 57062306a36Sopenharmony_ci } 57162306a36Sopenharmony_ci 57262306a36Sopenharmony_ci error = device_create_file(&hdev->dev, &dev_attr_operation_mode); 57362306a36Sopenharmony_ci if (error) { 57462306a36Sopenharmony_ci hid_err(hdev, "failed to create sysfs attributes\n"); 57562306a36Sopenharmony_ci goto err_cleanup_sysfs1; 57662306a36Sopenharmony_ci } 57762306a36Sopenharmony_ci 57862306a36Sopenharmony_ci if (data->status & PICOLCD_BOOTLOADER) 57962306a36Sopenharmony_ci error = picolcd_probe_bootloader(hdev, data); 58062306a36Sopenharmony_ci else 58162306a36Sopenharmony_ci error = picolcd_probe_lcd(hdev, data); 58262306a36Sopenharmony_ci if (error) 58362306a36Sopenharmony_ci goto err_cleanup_sysfs2; 58462306a36Sopenharmony_ci 58562306a36Sopenharmony_ci dbg_hid(PICOLCD_NAME " activated and initialized\n"); 58662306a36Sopenharmony_ci return 0; 58762306a36Sopenharmony_ci 58862306a36Sopenharmony_cierr_cleanup_sysfs2: 58962306a36Sopenharmony_ci device_remove_file(&hdev->dev, &dev_attr_operation_mode); 59062306a36Sopenharmony_cierr_cleanup_sysfs1: 59162306a36Sopenharmony_ci device_remove_file(&hdev->dev, &dev_attr_operation_mode_delay); 59262306a36Sopenharmony_cierr_cleanup_hid_ll: 59362306a36Sopenharmony_ci hid_hw_close(hdev); 59462306a36Sopenharmony_cierr_cleanup_hid_hw: 59562306a36Sopenharmony_ci hid_hw_stop(hdev); 59662306a36Sopenharmony_cierr_cleanup_data: 59762306a36Sopenharmony_ci kfree(data); 59862306a36Sopenharmony_ci return error; 59962306a36Sopenharmony_ci} 60062306a36Sopenharmony_ci 60162306a36Sopenharmony_cistatic void picolcd_remove(struct hid_device *hdev) 60262306a36Sopenharmony_ci{ 60362306a36Sopenharmony_ci struct picolcd_data *data = hid_get_drvdata(hdev); 60462306a36Sopenharmony_ci unsigned long flags; 60562306a36Sopenharmony_ci 60662306a36Sopenharmony_ci dbg_hid(PICOLCD_NAME " hardware remove...\n"); 60762306a36Sopenharmony_ci spin_lock_irqsave(&data->lock, flags); 60862306a36Sopenharmony_ci data->status |= PICOLCD_FAILED; 60962306a36Sopenharmony_ci spin_unlock_irqrestore(&data->lock, flags); 61062306a36Sopenharmony_ci 61162306a36Sopenharmony_ci picolcd_exit_devfs(data); 61262306a36Sopenharmony_ci device_remove_file(&hdev->dev, &dev_attr_operation_mode); 61362306a36Sopenharmony_ci device_remove_file(&hdev->dev, &dev_attr_operation_mode_delay); 61462306a36Sopenharmony_ci hid_hw_close(hdev); 61562306a36Sopenharmony_ci hid_hw_stop(hdev); 61662306a36Sopenharmony_ci 61762306a36Sopenharmony_ci /* Shortcut potential pending reply that will never arrive */ 61862306a36Sopenharmony_ci spin_lock_irqsave(&data->lock, flags); 61962306a36Sopenharmony_ci if (data->pending) 62062306a36Sopenharmony_ci complete(&data->pending->ready); 62162306a36Sopenharmony_ci spin_unlock_irqrestore(&data->lock, flags); 62262306a36Sopenharmony_ci 62362306a36Sopenharmony_ci /* Cleanup LED */ 62462306a36Sopenharmony_ci picolcd_exit_leds(data); 62562306a36Sopenharmony_ci /* Clean up the framebuffer */ 62662306a36Sopenharmony_ci picolcd_exit_backlight(data); 62762306a36Sopenharmony_ci picolcd_exit_lcd(data); 62862306a36Sopenharmony_ci picolcd_exit_framebuffer(data); 62962306a36Sopenharmony_ci /* Cleanup input */ 63062306a36Sopenharmony_ci picolcd_exit_cir(data); 63162306a36Sopenharmony_ci picolcd_exit_keys(data); 63262306a36Sopenharmony_ci 63362306a36Sopenharmony_ci mutex_destroy(&data->mutex); 63462306a36Sopenharmony_ci /* Finally, clean up the picolcd data itself */ 63562306a36Sopenharmony_ci kfree(data); 63662306a36Sopenharmony_ci} 63762306a36Sopenharmony_ci 63862306a36Sopenharmony_cistatic const struct hid_device_id picolcd_devices[] = { 63962306a36Sopenharmony_ci { HID_USB_DEVICE(USB_VENDOR_ID_MICROCHIP, USB_DEVICE_ID_PICOLCD) }, 64062306a36Sopenharmony_ci { HID_USB_DEVICE(USB_VENDOR_ID_MICROCHIP, USB_DEVICE_ID_PICOLCD_BOOTLOADER) }, 64162306a36Sopenharmony_ci { } 64262306a36Sopenharmony_ci}; 64362306a36Sopenharmony_ciMODULE_DEVICE_TABLE(hid, picolcd_devices); 64462306a36Sopenharmony_ci 64562306a36Sopenharmony_cistatic struct hid_driver picolcd_driver = { 64662306a36Sopenharmony_ci .name = "hid-picolcd", 64762306a36Sopenharmony_ci .id_table = picolcd_devices, 64862306a36Sopenharmony_ci .probe = picolcd_probe, 64962306a36Sopenharmony_ci .remove = picolcd_remove, 65062306a36Sopenharmony_ci .raw_event = picolcd_raw_event, 65162306a36Sopenharmony_ci#ifdef CONFIG_PM 65262306a36Sopenharmony_ci .suspend = picolcd_suspend, 65362306a36Sopenharmony_ci .resume = picolcd_resume, 65462306a36Sopenharmony_ci .reset_resume = picolcd_reset_resume, 65562306a36Sopenharmony_ci#endif 65662306a36Sopenharmony_ci}; 65762306a36Sopenharmony_cimodule_hid_driver(picolcd_driver); 65862306a36Sopenharmony_ci 65962306a36Sopenharmony_ciMODULE_DESCRIPTION("Minibox graphics PicoLCD Driver"); 66062306a36Sopenharmony_ciMODULE_LICENSE("GPL v2"); 661