162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Driver for IMS Passenger Control Unit Devices 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Copyright (C) 2013 The IMS Company 662306a36Sopenharmony_ci */ 762306a36Sopenharmony_ci 862306a36Sopenharmony_ci#include <linux/completion.h> 962306a36Sopenharmony_ci#include <linux/device.h> 1062306a36Sopenharmony_ci#include <linux/firmware.h> 1162306a36Sopenharmony_ci#include <linux/ihex.h> 1262306a36Sopenharmony_ci#include <linux/input.h> 1362306a36Sopenharmony_ci#include <linux/kernel.h> 1462306a36Sopenharmony_ci#include <linux/leds.h> 1562306a36Sopenharmony_ci#include <linux/module.h> 1662306a36Sopenharmony_ci#include <linux/slab.h> 1762306a36Sopenharmony_ci#include <linux/types.h> 1862306a36Sopenharmony_ci#include <linux/usb/input.h> 1962306a36Sopenharmony_ci#include <linux/usb/cdc.h> 2062306a36Sopenharmony_ci#include <asm/unaligned.h> 2162306a36Sopenharmony_ci 2262306a36Sopenharmony_ci#define IMS_PCU_KEYMAP_LEN 32 2362306a36Sopenharmony_ci 2462306a36Sopenharmony_cistruct ims_pcu_buttons { 2562306a36Sopenharmony_ci struct input_dev *input; 2662306a36Sopenharmony_ci char name[32]; 2762306a36Sopenharmony_ci char phys[32]; 2862306a36Sopenharmony_ci unsigned short keymap[IMS_PCU_KEYMAP_LEN]; 2962306a36Sopenharmony_ci}; 3062306a36Sopenharmony_ci 3162306a36Sopenharmony_cistruct ims_pcu_gamepad { 3262306a36Sopenharmony_ci struct input_dev *input; 3362306a36Sopenharmony_ci char name[32]; 3462306a36Sopenharmony_ci char phys[32]; 3562306a36Sopenharmony_ci}; 3662306a36Sopenharmony_ci 3762306a36Sopenharmony_cistruct ims_pcu_backlight { 3862306a36Sopenharmony_ci struct led_classdev cdev; 3962306a36Sopenharmony_ci char name[32]; 4062306a36Sopenharmony_ci}; 4162306a36Sopenharmony_ci 4262306a36Sopenharmony_ci#define IMS_PCU_PART_NUMBER_LEN 15 4362306a36Sopenharmony_ci#define IMS_PCU_SERIAL_NUMBER_LEN 8 4462306a36Sopenharmony_ci#define IMS_PCU_DOM_LEN 8 4562306a36Sopenharmony_ci#define IMS_PCU_FW_VERSION_LEN (9 + 1) 4662306a36Sopenharmony_ci#define IMS_PCU_BL_VERSION_LEN (9 + 1) 4762306a36Sopenharmony_ci#define IMS_PCU_BL_RESET_REASON_LEN (2 + 1) 4862306a36Sopenharmony_ci 4962306a36Sopenharmony_ci#define IMS_PCU_PCU_B_DEVICE_ID 5 5062306a36Sopenharmony_ci 5162306a36Sopenharmony_ci#define IMS_PCU_BUF_SIZE 128 5262306a36Sopenharmony_ci 5362306a36Sopenharmony_cistruct ims_pcu { 5462306a36Sopenharmony_ci struct usb_device *udev; 5562306a36Sopenharmony_ci struct device *dev; /* control interface's device, used for logging */ 5662306a36Sopenharmony_ci 5762306a36Sopenharmony_ci unsigned int device_no; 5862306a36Sopenharmony_ci 5962306a36Sopenharmony_ci bool bootloader_mode; 6062306a36Sopenharmony_ci 6162306a36Sopenharmony_ci char part_number[IMS_PCU_PART_NUMBER_LEN]; 6262306a36Sopenharmony_ci char serial_number[IMS_PCU_SERIAL_NUMBER_LEN]; 6362306a36Sopenharmony_ci char date_of_manufacturing[IMS_PCU_DOM_LEN]; 6462306a36Sopenharmony_ci char fw_version[IMS_PCU_FW_VERSION_LEN]; 6562306a36Sopenharmony_ci char bl_version[IMS_PCU_BL_VERSION_LEN]; 6662306a36Sopenharmony_ci char reset_reason[IMS_PCU_BL_RESET_REASON_LEN]; 6762306a36Sopenharmony_ci int update_firmware_status; 6862306a36Sopenharmony_ci u8 device_id; 6962306a36Sopenharmony_ci 7062306a36Sopenharmony_ci u8 ofn_reg_addr; 7162306a36Sopenharmony_ci 7262306a36Sopenharmony_ci struct usb_interface *ctrl_intf; 7362306a36Sopenharmony_ci 7462306a36Sopenharmony_ci struct usb_endpoint_descriptor *ep_ctrl; 7562306a36Sopenharmony_ci struct urb *urb_ctrl; 7662306a36Sopenharmony_ci u8 *urb_ctrl_buf; 7762306a36Sopenharmony_ci dma_addr_t ctrl_dma; 7862306a36Sopenharmony_ci size_t max_ctrl_size; 7962306a36Sopenharmony_ci 8062306a36Sopenharmony_ci struct usb_interface *data_intf; 8162306a36Sopenharmony_ci 8262306a36Sopenharmony_ci struct usb_endpoint_descriptor *ep_in; 8362306a36Sopenharmony_ci struct urb *urb_in; 8462306a36Sopenharmony_ci u8 *urb_in_buf; 8562306a36Sopenharmony_ci dma_addr_t read_dma; 8662306a36Sopenharmony_ci size_t max_in_size; 8762306a36Sopenharmony_ci 8862306a36Sopenharmony_ci struct usb_endpoint_descriptor *ep_out; 8962306a36Sopenharmony_ci u8 *urb_out_buf; 9062306a36Sopenharmony_ci size_t max_out_size; 9162306a36Sopenharmony_ci 9262306a36Sopenharmony_ci u8 read_buf[IMS_PCU_BUF_SIZE]; 9362306a36Sopenharmony_ci u8 read_pos; 9462306a36Sopenharmony_ci u8 check_sum; 9562306a36Sopenharmony_ci bool have_stx; 9662306a36Sopenharmony_ci bool have_dle; 9762306a36Sopenharmony_ci 9862306a36Sopenharmony_ci u8 cmd_buf[IMS_PCU_BUF_SIZE]; 9962306a36Sopenharmony_ci u8 ack_id; 10062306a36Sopenharmony_ci u8 expected_response; 10162306a36Sopenharmony_ci u8 cmd_buf_len; 10262306a36Sopenharmony_ci struct completion cmd_done; 10362306a36Sopenharmony_ci struct mutex cmd_mutex; 10462306a36Sopenharmony_ci 10562306a36Sopenharmony_ci u32 fw_start_addr; 10662306a36Sopenharmony_ci u32 fw_end_addr; 10762306a36Sopenharmony_ci struct completion async_firmware_done; 10862306a36Sopenharmony_ci 10962306a36Sopenharmony_ci struct ims_pcu_buttons buttons; 11062306a36Sopenharmony_ci struct ims_pcu_gamepad *gamepad; 11162306a36Sopenharmony_ci struct ims_pcu_backlight backlight; 11262306a36Sopenharmony_ci 11362306a36Sopenharmony_ci bool setup_complete; /* Input and LED devices have been created */ 11462306a36Sopenharmony_ci}; 11562306a36Sopenharmony_ci 11662306a36Sopenharmony_ci 11762306a36Sopenharmony_ci/********************************************************************* 11862306a36Sopenharmony_ci * Buttons Input device support * 11962306a36Sopenharmony_ci *********************************************************************/ 12062306a36Sopenharmony_ci 12162306a36Sopenharmony_cistatic const unsigned short ims_pcu_keymap_1[] = { 12262306a36Sopenharmony_ci [1] = KEY_ATTENDANT_OFF, 12362306a36Sopenharmony_ci [2] = KEY_ATTENDANT_ON, 12462306a36Sopenharmony_ci [3] = KEY_LIGHTS_TOGGLE, 12562306a36Sopenharmony_ci [4] = KEY_VOLUMEUP, 12662306a36Sopenharmony_ci [5] = KEY_VOLUMEDOWN, 12762306a36Sopenharmony_ci [6] = KEY_INFO, 12862306a36Sopenharmony_ci}; 12962306a36Sopenharmony_ci 13062306a36Sopenharmony_cistatic const unsigned short ims_pcu_keymap_2[] = { 13162306a36Sopenharmony_ci [4] = KEY_VOLUMEUP, 13262306a36Sopenharmony_ci [5] = KEY_VOLUMEDOWN, 13362306a36Sopenharmony_ci [6] = KEY_INFO, 13462306a36Sopenharmony_ci}; 13562306a36Sopenharmony_ci 13662306a36Sopenharmony_cistatic const unsigned short ims_pcu_keymap_3[] = { 13762306a36Sopenharmony_ci [1] = KEY_HOMEPAGE, 13862306a36Sopenharmony_ci [2] = KEY_ATTENDANT_TOGGLE, 13962306a36Sopenharmony_ci [3] = KEY_LIGHTS_TOGGLE, 14062306a36Sopenharmony_ci [4] = KEY_VOLUMEUP, 14162306a36Sopenharmony_ci [5] = KEY_VOLUMEDOWN, 14262306a36Sopenharmony_ci [6] = KEY_DISPLAYTOGGLE, 14362306a36Sopenharmony_ci [18] = KEY_PLAYPAUSE, 14462306a36Sopenharmony_ci}; 14562306a36Sopenharmony_ci 14662306a36Sopenharmony_cistatic const unsigned short ims_pcu_keymap_4[] = { 14762306a36Sopenharmony_ci [1] = KEY_ATTENDANT_OFF, 14862306a36Sopenharmony_ci [2] = KEY_ATTENDANT_ON, 14962306a36Sopenharmony_ci [3] = KEY_LIGHTS_TOGGLE, 15062306a36Sopenharmony_ci [4] = KEY_VOLUMEUP, 15162306a36Sopenharmony_ci [5] = KEY_VOLUMEDOWN, 15262306a36Sopenharmony_ci [6] = KEY_INFO, 15362306a36Sopenharmony_ci [18] = KEY_PLAYPAUSE, 15462306a36Sopenharmony_ci}; 15562306a36Sopenharmony_ci 15662306a36Sopenharmony_cistatic const unsigned short ims_pcu_keymap_5[] = { 15762306a36Sopenharmony_ci [1] = KEY_ATTENDANT_OFF, 15862306a36Sopenharmony_ci [2] = KEY_ATTENDANT_ON, 15962306a36Sopenharmony_ci [3] = KEY_LIGHTS_TOGGLE, 16062306a36Sopenharmony_ci}; 16162306a36Sopenharmony_ci 16262306a36Sopenharmony_cistruct ims_pcu_device_info { 16362306a36Sopenharmony_ci const unsigned short *keymap; 16462306a36Sopenharmony_ci size_t keymap_len; 16562306a36Sopenharmony_ci bool has_gamepad; 16662306a36Sopenharmony_ci}; 16762306a36Sopenharmony_ci 16862306a36Sopenharmony_ci#define IMS_PCU_DEVINFO(_n, _gamepad) \ 16962306a36Sopenharmony_ci [_n] = { \ 17062306a36Sopenharmony_ci .keymap = ims_pcu_keymap_##_n, \ 17162306a36Sopenharmony_ci .keymap_len = ARRAY_SIZE(ims_pcu_keymap_##_n), \ 17262306a36Sopenharmony_ci .has_gamepad = _gamepad, \ 17362306a36Sopenharmony_ci } 17462306a36Sopenharmony_ci 17562306a36Sopenharmony_cistatic const struct ims_pcu_device_info ims_pcu_device_info[] = { 17662306a36Sopenharmony_ci IMS_PCU_DEVINFO(1, true), 17762306a36Sopenharmony_ci IMS_PCU_DEVINFO(2, true), 17862306a36Sopenharmony_ci IMS_PCU_DEVINFO(3, true), 17962306a36Sopenharmony_ci IMS_PCU_DEVINFO(4, true), 18062306a36Sopenharmony_ci IMS_PCU_DEVINFO(5, false), 18162306a36Sopenharmony_ci}; 18262306a36Sopenharmony_ci 18362306a36Sopenharmony_cistatic void ims_pcu_buttons_report(struct ims_pcu *pcu, u32 data) 18462306a36Sopenharmony_ci{ 18562306a36Sopenharmony_ci struct ims_pcu_buttons *buttons = &pcu->buttons; 18662306a36Sopenharmony_ci struct input_dev *input = buttons->input; 18762306a36Sopenharmony_ci int i; 18862306a36Sopenharmony_ci 18962306a36Sopenharmony_ci for (i = 0; i < 32; i++) { 19062306a36Sopenharmony_ci unsigned short keycode = buttons->keymap[i]; 19162306a36Sopenharmony_ci 19262306a36Sopenharmony_ci if (keycode != KEY_RESERVED) 19362306a36Sopenharmony_ci input_report_key(input, keycode, data & (1UL << i)); 19462306a36Sopenharmony_ci } 19562306a36Sopenharmony_ci 19662306a36Sopenharmony_ci input_sync(input); 19762306a36Sopenharmony_ci} 19862306a36Sopenharmony_ci 19962306a36Sopenharmony_cistatic int ims_pcu_setup_buttons(struct ims_pcu *pcu, 20062306a36Sopenharmony_ci const unsigned short *keymap, 20162306a36Sopenharmony_ci size_t keymap_len) 20262306a36Sopenharmony_ci{ 20362306a36Sopenharmony_ci struct ims_pcu_buttons *buttons = &pcu->buttons; 20462306a36Sopenharmony_ci struct input_dev *input; 20562306a36Sopenharmony_ci int i; 20662306a36Sopenharmony_ci int error; 20762306a36Sopenharmony_ci 20862306a36Sopenharmony_ci input = input_allocate_device(); 20962306a36Sopenharmony_ci if (!input) { 21062306a36Sopenharmony_ci dev_err(pcu->dev, 21162306a36Sopenharmony_ci "Not enough memory for input input device\n"); 21262306a36Sopenharmony_ci return -ENOMEM; 21362306a36Sopenharmony_ci } 21462306a36Sopenharmony_ci 21562306a36Sopenharmony_ci snprintf(buttons->name, sizeof(buttons->name), 21662306a36Sopenharmony_ci "IMS PCU#%d Button Interface", pcu->device_no); 21762306a36Sopenharmony_ci 21862306a36Sopenharmony_ci usb_make_path(pcu->udev, buttons->phys, sizeof(buttons->phys)); 21962306a36Sopenharmony_ci strlcat(buttons->phys, "/input0", sizeof(buttons->phys)); 22062306a36Sopenharmony_ci 22162306a36Sopenharmony_ci memcpy(buttons->keymap, keymap, sizeof(*keymap) * keymap_len); 22262306a36Sopenharmony_ci 22362306a36Sopenharmony_ci input->name = buttons->name; 22462306a36Sopenharmony_ci input->phys = buttons->phys; 22562306a36Sopenharmony_ci usb_to_input_id(pcu->udev, &input->id); 22662306a36Sopenharmony_ci input->dev.parent = &pcu->ctrl_intf->dev; 22762306a36Sopenharmony_ci 22862306a36Sopenharmony_ci input->keycode = buttons->keymap; 22962306a36Sopenharmony_ci input->keycodemax = ARRAY_SIZE(buttons->keymap); 23062306a36Sopenharmony_ci input->keycodesize = sizeof(buttons->keymap[0]); 23162306a36Sopenharmony_ci 23262306a36Sopenharmony_ci __set_bit(EV_KEY, input->evbit); 23362306a36Sopenharmony_ci for (i = 0; i < IMS_PCU_KEYMAP_LEN; i++) 23462306a36Sopenharmony_ci __set_bit(buttons->keymap[i], input->keybit); 23562306a36Sopenharmony_ci __clear_bit(KEY_RESERVED, input->keybit); 23662306a36Sopenharmony_ci 23762306a36Sopenharmony_ci error = input_register_device(input); 23862306a36Sopenharmony_ci if (error) { 23962306a36Sopenharmony_ci dev_err(pcu->dev, 24062306a36Sopenharmony_ci "Failed to register buttons input device: %d\n", 24162306a36Sopenharmony_ci error); 24262306a36Sopenharmony_ci input_free_device(input); 24362306a36Sopenharmony_ci return error; 24462306a36Sopenharmony_ci } 24562306a36Sopenharmony_ci 24662306a36Sopenharmony_ci buttons->input = input; 24762306a36Sopenharmony_ci return 0; 24862306a36Sopenharmony_ci} 24962306a36Sopenharmony_ci 25062306a36Sopenharmony_cistatic void ims_pcu_destroy_buttons(struct ims_pcu *pcu) 25162306a36Sopenharmony_ci{ 25262306a36Sopenharmony_ci struct ims_pcu_buttons *buttons = &pcu->buttons; 25362306a36Sopenharmony_ci 25462306a36Sopenharmony_ci input_unregister_device(buttons->input); 25562306a36Sopenharmony_ci} 25662306a36Sopenharmony_ci 25762306a36Sopenharmony_ci 25862306a36Sopenharmony_ci/********************************************************************* 25962306a36Sopenharmony_ci * Gamepad Input device support * 26062306a36Sopenharmony_ci *********************************************************************/ 26162306a36Sopenharmony_ci 26262306a36Sopenharmony_cistatic void ims_pcu_gamepad_report(struct ims_pcu *pcu, u32 data) 26362306a36Sopenharmony_ci{ 26462306a36Sopenharmony_ci struct ims_pcu_gamepad *gamepad = pcu->gamepad; 26562306a36Sopenharmony_ci struct input_dev *input = gamepad->input; 26662306a36Sopenharmony_ci int x, y; 26762306a36Sopenharmony_ci 26862306a36Sopenharmony_ci x = !!(data & (1 << 14)) - !!(data & (1 << 13)); 26962306a36Sopenharmony_ci y = !!(data & (1 << 12)) - !!(data & (1 << 11)); 27062306a36Sopenharmony_ci 27162306a36Sopenharmony_ci input_report_abs(input, ABS_X, x); 27262306a36Sopenharmony_ci input_report_abs(input, ABS_Y, y); 27362306a36Sopenharmony_ci 27462306a36Sopenharmony_ci input_report_key(input, BTN_A, data & (1 << 7)); 27562306a36Sopenharmony_ci input_report_key(input, BTN_B, data & (1 << 8)); 27662306a36Sopenharmony_ci input_report_key(input, BTN_X, data & (1 << 9)); 27762306a36Sopenharmony_ci input_report_key(input, BTN_Y, data & (1 << 10)); 27862306a36Sopenharmony_ci input_report_key(input, BTN_START, data & (1 << 15)); 27962306a36Sopenharmony_ci input_report_key(input, BTN_SELECT, data & (1 << 16)); 28062306a36Sopenharmony_ci 28162306a36Sopenharmony_ci input_sync(input); 28262306a36Sopenharmony_ci} 28362306a36Sopenharmony_ci 28462306a36Sopenharmony_cistatic int ims_pcu_setup_gamepad(struct ims_pcu *pcu) 28562306a36Sopenharmony_ci{ 28662306a36Sopenharmony_ci struct ims_pcu_gamepad *gamepad; 28762306a36Sopenharmony_ci struct input_dev *input; 28862306a36Sopenharmony_ci int error; 28962306a36Sopenharmony_ci 29062306a36Sopenharmony_ci gamepad = kzalloc(sizeof(struct ims_pcu_gamepad), GFP_KERNEL); 29162306a36Sopenharmony_ci input = input_allocate_device(); 29262306a36Sopenharmony_ci if (!gamepad || !input) { 29362306a36Sopenharmony_ci dev_err(pcu->dev, 29462306a36Sopenharmony_ci "Not enough memory for gamepad device\n"); 29562306a36Sopenharmony_ci error = -ENOMEM; 29662306a36Sopenharmony_ci goto err_free_mem; 29762306a36Sopenharmony_ci } 29862306a36Sopenharmony_ci 29962306a36Sopenharmony_ci gamepad->input = input; 30062306a36Sopenharmony_ci 30162306a36Sopenharmony_ci snprintf(gamepad->name, sizeof(gamepad->name), 30262306a36Sopenharmony_ci "IMS PCU#%d Gamepad Interface", pcu->device_no); 30362306a36Sopenharmony_ci 30462306a36Sopenharmony_ci usb_make_path(pcu->udev, gamepad->phys, sizeof(gamepad->phys)); 30562306a36Sopenharmony_ci strlcat(gamepad->phys, "/input1", sizeof(gamepad->phys)); 30662306a36Sopenharmony_ci 30762306a36Sopenharmony_ci input->name = gamepad->name; 30862306a36Sopenharmony_ci input->phys = gamepad->phys; 30962306a36Sopenharmony_ci usb_to_input_id(pcu->udev, &input->id); 31062306a36Sopenharmony_ci input->dev.parent = &pcu->ctrl_intf->dev; 31162306a36Sopenharmony_ci 31262306a36Sopenharmony_ci __set_bit(EV_KEY, input->evbit); 31362306a36Sopenharmony_ci __set_bit(BTN_A, input->keybit); 31462306a36Sopenharmony_ci __set_bit(BTN_B, input->keybit); 31562306a36Sopenharmony_ci __set_bit(BTN_X, input->keybit); 31662306a36Sopenharmony_ci __set_bit(BTN_Y, input->keybit); 31762306a36Sopenharmony_ci __set_bit(BTN_START, input->keybit); 31862306a36Sopenharmony_ci __set_bit(BTN_SELECT, input->keybit); 31962306a36Sopenharmony_ci 32062306a36Sopenharmony_ci __set_bit(EV_ABS, input->evbit); 32162306a36Sopenharmony_ci input_set_abs_params(input, ABS_X, -1, 1, 0, 0); 32262306a36Sopenharmony_ci input_set_abs_params(input, ABS_Y, -1, 1, 0, 0); 32362306a36Sopenharmony_ci 32462306a36Sopenharmony_ci error = input_register_device(input); 32562306a36Sopenharmony_ci if (error) { 32662306a36Sopenharmony_ci dev_err(pcu->dev, 32762306a36Sopenharmony_ci "Failed to register gamepad input device: %d\n", 32862306a36Sopenharmony_ci error); 32962306a36Sopenharmony_ci goto err_free_mem; 33062306a36Sopenharmony_ci } 33162306a36Sopenharmony_ci 33262306a36Sopenharmony_ci pcu->gamepad = gamepad; 33362306a36Sopenharmony_ci return 0; 33462306a36Sopenharmony_ci 33562306a36Sopenharmony_cierr_free_mem: 33662306a36Sopenharmony_ci input_free_device(input); 33762306a36Sopenharmony_ci kfree(gamepad); 33862306a36Sopenharmony_ci return error; 33962306a36Sopenharmony_ci} 34062306a36Sopenharmony_ci 34162306a36Sopenharmony_cistatic void ims_pcu_destroy_gamepad(struct ims_pcu *pcu) 34262306a36Sopenharmony_ci{ 34362306a36Sopenharmony_ci struct ims_pcu_gamepad *gamepad = pcu->gamepad; 34462306a36Sopenharmony_ci 34562306a36Sopenharmony_ci input_unregister_device(gamepad->input); 34662306a36Sopenharmony_ci kfree(gamepad); 34762306a36Sopenharmony_ci} 34862306a36Sopenharmony_ci 34962306a36Sopenharmony_ci 35062306a36Sopenharmony_ci/********************************************************************* 35162306a36Sopenharmony_ci * PCU Communication protocol handling * 35262306a36Sopenharmony_ci *********************************************************************/ 35362306a36Sopenharmony_ci 35462306a36Sopenharmony_ci#define IMS_PCU_PROTOCOL_STX 0x02 35562306a36Sopenharmony_ci#define IMS_PCU_PROTOCOL_ETX 0x03 35662306a36Sopenharmony_ci#define IMS_PCU_PROTOCOL_DLE 0x10 35762306a36Sopenharmony_ci 35862306a36Sopenharmony_ci/* PCU commands */ 35962306a36Sopenharmony_ci#define IMS_PCU_CMD_STATUS 0xa0 36062306a36Sopenharmony_ci#define IMS_PCU_CMD_PCU_RESET 0xa1 36162306a36Sopenharmony_ci#define IMS_PCU_CMD_RESET_REASON 0xa2 36262306a36Sopenharmony_ci#define IMS_PCU_CMD_SEND_BUTTONS 0xa3 36362306a36Sopenharmony_ci#define IMS_PCU_CMD_JUMP_TO_BTLDR 0xa4 36462306a36Sopenharmony_ci#define IMS_PCU_CMD_GET_INFO 0xa5 36562306a36Sopenharmony_ci#define IMS_PCU_CMD_SET_BRIGHTNESS 0xa6 36662306a36Sopenharmony_ci#define IMS_PCU_CMD_EEPROM 0xa7 36762306a36Sopenharmony_ci#define IMS_PCU_CMD_GET_FW_VERSION 0xa8 36862306a36Sopenharmony_ci#define IMS_PCU_CMD_GET_BL_VERSION 0xa9 36962306a36Sopenharmony_ci#define IMS_PCU_CMD_SET_INFO 0xab 37062306a36Sopenharmony_ci#define IMS_PCU_CMD_GET_BRIGHTNESS 0xac 37162306a36Sopenharmony_ci#define IMS_PCU_CMD_GET_DEVICE_ID 0xae 37262306a36Sopenharmony_ci#define IMS_PCU_CMD_SPECIAL_INFO 0xb0 37362306a36Sopenharmony_ci#define IMS_PCU_CMD_BOOTLOADER 0xb1 /* Pass data to bootloader */ 37462306a36Sopenharmony_ci#define IMS_PCU_CMD_OFN_SET_CONFIG 0xb3 37562306a36Sopenharmony_ci#define IMS_PCU_CMD_OFN_GET_CONFIG 0xb4 37662306a36Sopenharmony_ci 37762306a36Sopenharmony_ci/* PCU responses */ 37862306a36Sopenharmony_ci#define IMS_PCU_RSP_STATUS 0xc0 37962306a36Sopenharmony_ci#define IMS_PCU_RSP_PCU_RESET 0 /* Originally 0xc1 */ 38062306a36Sopenharmony_ci#define IMS_PCU_RSP_RESET_REASON 0xc2 38162306a36Sopenharmony_ci#define IMS_PCU_RSP_SEND_BUTTONS 0xc3 38262306a36Sopenharmony_ci#define IMS_PCU_RSP_JUMP_TO_BTLDR 0 /* Originally 0xc4 */ 38362306a36Sopenharmony_ci#define IMS_PCU_RSP_GET_INFO 0xc5 38462306a36Sopenharmony_ci#define IMS_PCU_RSP_SET_BRIGHTNESS 0xc6 38562306a36Sopenharmony_ci#define IMS_PCU_RSP_EEPROM 0xc7 38662306a36Sopenharmony_ci#define IMS_PCU_RSP_GET_FW_VERSION 0xc8 38762306a36Sopenharmony_ci#define IMS_PCU_RSP_GET_BL_VERSION 0xc9 38862306a36Sopenharmony_ci#define IMS_PCU_RSP_SET_INFO 0xcb 38962306a36Sopenharmony_ci#define IMS_PCU_RSP_GET_BRIGHTNESS 0xcc 39062306a36Sopenharmony_ci#define IMS_PCU_RSP_CMD_INVALID 0xcd 39162306a36Sopenharmony_ci#define IMS_PCU_RSP_GET_DEVICE_ID 0xce 39262306a36Sopenharmony_ci#define IMS_PCU_RSP_SPECIAL_INFO 0xd0 39362306a36Sopenharmony_ci#define IMS_PCU_RSP_BOOTLOADER 0xd1 /* Bootloader response */ 39462306a36Sopenharmony_ci#define IMS_PCU_RSP_OFN_SET_CONFIG 0xd2 39562306a36Sopenharmony_ci#define IMS_PCU_RSP_OFN_GET_CONFIG 0xd3 39662306a36Sopenharmony_ci 39762306a36Sopenharmony_ci 39862306a36Sopenharmony_ci#define IMS_PCU_RSP_EVNT_BUTTONS 0xe0 /* Unsolicited, button state */ 39962306a36Sopenharmony_ci#define IMS_PCU_GAMEPAD_MASK 0x0001ff80UL /* Bits 7 through 16 */ 40062306a36Sopenharmony_ci 40162306a36Sopenharmony_ci 40262306a36Sopenharmony_ci#define IMS_PCU_MIN_PACKET_LEN 3 40362306a36Sopenharmony_ci#define IMS_PCU_DATA_OFFSET 2 40462306a36Sopenharmony_ci 40562306a36Sopenharmony_ci#define IMS_PCU_CMD_WRITE_TIMEOUT 100 /* msec */ 40662306a36Sopenharmony_ci#define IMS_PCU_CMD_RESPONSE_TIMEOUT 500 /* msec */ 40762306a36Sopenharmony_ci 40862306a36Sopenharmony_cistatic void ims_pcu_report_events(struct ims_pcu *pcu) 40962306a36Sopenharmony_ci{ 41062306a36Sopenharmony_ci u32 data = get_unaligned_be32(&pcu->read_buf[3]); 41162306a36Sopenharmony_ci 41262306a36Sopenharmony_ci ims_pcu_buttons_report(pcu, data & ~IMS_PCU_GAMEPAD_MASK); 41362306a36Sopenharmony_ci if (pcu->gamepad) 41462306a36Sopenharmony_ci ims_pcu_gamepad_report(pcu, data); 41562306a36Sopenharmony_ci} 41662306a36Sopenharmony_ci 41762306a36Sopenharmony_cistatic void ims_pcu_handle_response(struct ims_pcu *pcu) 41862306a36Sopenharmony_ci{ 41962306a36Sopenharmony_ci switch (pcu->read_buf[0]) { 42062306a36Sopenharmony_ci case IMS_PCU_RSP_EVNT_BUTTONS: 42162306a36Sopenharmony_ci if (likely(pcu->setup_complete)) 42262306a36Sopenharmony_ci ims_pcu_report_events(pcu); 42362306a36Sopenharmony_ci break; 42462306a36Sopenharmony_ci 42562306a36Sopenharmony_ci default: 42662306a36Sopenharmony_ci /* 42762306a36Sopenharmony_ci * See if we got command completion. 42862306a36Sopenharmony_ci * If both the sequence and response code match save 42962306a36Sopenharmony_ci * the data and signal completion. 43062306a36Sopenharmony_ci */ 43162306a36Sopenharmony_ci if (pcu->read_buf[0] == pcu->expected_response && 43262306a36Sopenharmony_ci pcu->read_buf[1] == pcu->ack_id - 1) { 43362306a36Sopenharmony_ci 43462306a36Sopenharmony_ci memcpy(pcu->cmd_buf, pcu->read_buf, pcu->read_pos); 43562306a36Sopenharmony_ci pcu->cmd_buf_len = pcu->read_pos; 43662306a36Sopenharmony_ci complete(&pcu->cmd_done); 43762306a36Sopenharmony_ci } 43862306a36Sopenharmony_ci break; 43962306a36Sopenharmony_ci } 44062306a36Sopenharmony_ci} 44162306a36Sopenharmony_ci 44262306a36Sopenharmony_cistatic void ims_pcu_process_data(struct ims_pcu *pcu, struct urb *urb) 44362306a36Sopenharmony_ci{ 44462306a36Sopenharmony_ci int i; 44562306a36Sopenharmony_ci 44662306a36Sopenharmony_ci for (i = 0; i < urb->actual_length; i++) { 44762306a36Sopenharmony_ci u8 data = pcu->urb_in_buf[i]; 44862306a36Sopenharmony_ci 44962306a36Sopenharmony_ci /* Skip everything until we get Start Xmit */ 45062306a36Sopenharmony_ci if (!pcu->have_stx && data != IMS_PCU_PROTOCOL_STX) 45162306a36Sopenharmony_ci continue; 45262306a36Sopenharmony_ci 45362306a36Sopenharmony_ci if (pcu->have_dle) { 45462306a36Sopenharmony_ci pcu->have_dle = false; 45562306a36Sopenharmony_ci pcu->read_buf[pcu->read_pos++] = data; 45662306a36Sopenharmony_ci pcu->check_sum += data; 45762306a36Sopenharmony_ci continue; 45862306a36Sopenharmony_ci } 45962306a36Sopenharmony_ci 46062306a36Sopenharmony_ci switch (data) { 46162306a36Sopenharmony_ci case IMS_PCU_PROTOCOL_STX: 46262306a36Sopenharmony_ci if (pcu->have_stx) 46362306a36Sopenharmony_ci dev_warn(pcu->dev, 46462306a36Sopenharmony_ci "Unexpected STX at byte %d, discarding old data\n", 46562306a36Sopenharmony_ci pcu->read_pos); 46662306a36Sopenharmony_ci pcu->have_stx = true; 46762306a36Sopenharmony_ci pcu->have_dle = false; 46862306a36Sopenharmony_ci pcu->read_pos = 0; 46962306a36Sopenharmony_ci pcu->check_sum = 0; 47062306a36Sopenharmony_ci break; 47162306a36Sopenharmony_ci 47262306a36Sopenharmony_ci case IMS_PCU_PROTOCOL_DLE: 47362306a36Sopenharmony_ci pcu->have_dle = true; 47462306a36Sopenharmony_ci break; 47562306a36Sopenharmony_ci 47662306a36Sopenharmony_ci case IMS_PCU_PROTOCOL_ETX: 47762306a36Sopenharmony_ci if (pcu->read_pos < IMS_PCU_MIN_PACKET_LEN) { 47862306a36Sopenharmony_ci dev_warn(pcu->dev, 47962306a36Sopenharmony_ci "Short packet received (%d bytes), ignoring\n", 48062306a36Sopenharmony_ci pcu->read_pos); 48162306a36Sopenharmony_ci } else if (pcu->check_sum != 0) { 48262306a36Sopenharmony_ci dev_warn(pcu->dev, 48362306a36Sopenharmony_ci "Invalid checksum in packet (%d bytes), ignoring\n", 48462306a36Sopenharmony_ci pcu->read_pos); 48562306a36Sopenharmony_ci } else { 48662306a36Sopenharmony_ci ims_pcu_handle_response(pcu); 48762306a36Sopenharmony_ci } 48862306a36Sopenharmony_ci 48962306a36Sopenharmony_ci pcu->have_stx = false; 49062306a36Sopenharmony_ci pcu->have_dle = false; 49162306a36Sopenharmony_ci pcu->read_pos = 0; 49262306a36Sopenharmony_ci break; 49362306a36Sopenharmony_ci 49462306a36Sopenharmony_ci default: 49562306a36Sopenharmony_ci pcu->read_buf[pcu->read_pos++] = data; 49662306a36Sopenharmony_ci pcu->check_sum += data; 49762306a36Sopenharmony_ci break; 49862306a36Sopenharmony_ci } 49962306a36Sopenharmony_ci } 50062306a36Sopenharmony_ci} 50162306a36Sopenharmony_ci 50262306a36Sopenharmony_cistatic bool ims_pcu_byte_needs_escape(u8 byte) 50362306a36Sopenharmony_ci{ 50462306a36Sopenharmony_ci return byte == IMS_PCU_PROTOCOL_STX || 50562306a36Sopenharmony_ci byte == IMS_PCU_PROTOCOL_ETX || 50662306a36Sopenharmony_ci byte == IMS_PCU_PROTOCOL_DLE; 50762306a36Sopenharmony_ci} 50862306a36Sopenharmony_ci 50962306a36Sopenharmony_cistatic int ims_pcu_send_cmd_chunk(struct ims_pcu *pcu, 51062306a36Sopenharmony_ci u8 command, int chunk, int len) 51162306a36Sopenharmony_ci{ 51262306a36Sopenharmony_ci int error; 51362306a36Sopenharmony_ci 51462306a36Sopenharmony_ci error = usb_bulk_msg(pcu->udev, 51562306a36Sopenharmony_ci usb_sndbulkpipe(pcu->udev, 51662306a36Sopenharmony_ci pcu->ep_out->bEndpointAddress), 51762306a36Sopenharmony_ci pcu->urb_out_buf, len, 51862306a36Sopenharmony_ci NULL, IMS_PCU_CMD_WRITE_TIMEOUT); 51962306a36Sopenharmony_ci if (error < 0) { 52062306a36Sopenharmony_ci dev_dbg(pcu->dev, 52162306a36Sopenharmony_ci "Sending 0x%02x command failed at chunk %d: %d\n", 52262306a36Sopenharmony_ci command, chunk, error); 52362306a36Sopenharmony_ci return error; 52462306a36Sopenharmony_ci } 52562306a36Sopenharmony_ci 52662306a36Sopenharmony_ci return 0; 52762306a36Sopenharmony_ci} 52862306a36Sopenharmony_ci 52962306a36Sopenharmony_cistatic int ims_pcu_send_command(struct ims_pcu *pcu, 53062306a36Sopenharmony_ci u8 command, const u8 *data, int len) 53162306a36Sopenharmony_ci{ 53262306a36Sopenharmony_ci int count = 0; 53362306a36Sopenharmony_ci int chunk = 0; 53462306a36Sopenharmony_ci int delta; 53562306a36Sopenharmony_ci int i; 53662306a36Sopenharmony_ci int error; 53762306a36Sopenharmony_ci u8 csum = 0; 53862306a36Sopenharmony_ci u8 ack_id; 53962306a36Sopenharmony_ci 54062306a36Sopenharmony_ci pcu->urb_out_buf[count++] = IMS_PCU_PROTOCOL_STX; 54162306a36Sopenharmony_ci 54262306a36Sopenharmony_ci /* We know the command need not be escaped */ 54362306a36Sopenharmony_ci pcu->urb_out_buf[count++] = command; 54462306a36Sopenharmony_ci csum += command; 54562306a36Sopenharmony_ci 54662306a36Sopenharmony_ci ack_id = pcu->ack_id++; 54762306a36Sopenharmony_ci if (ack_id == 0xff) 54862306a36Sopenharmony_ci ack_id = pcu->ack_id++; 54962306a36Sopenharmony_ci 55062306a36Sopenharmony_ci if (ims_pcu_byte_needs_escape(ack_id)) 55162306a36Sopenharmony_ci pcu->urb_out_buf[count++] = IMS_PCU_PROTOCOL_DLE; 55262306a36Sopenharmony_ci 55362306a36Sopenharmony_ci pcu->urb_out_buf[count++] = ack_id; 55462306a36Sopenharmony_ci csum += ack_id; 55562306a36Sopenharmony_ci 55662306a36Sopenharmony_ci for (i = 0; i < len; i++) { 55762306a36Sopenharmony_ci 55862306a36Sopenharmony_ci delta = ims_pcu_byte_needs_escape(data[i]) ? 2 : 1; 55962306a36Sopenharmony_ci if (count + delta >= pcu->max_out_size) { 56062306a36Sopenharmony_ci error = ims_pcu_send_cmd_chunk(pcu, command, 56162306a36Sopenharmony_ci ++chunk, count); 56262306a36Sopenharmony_ci if (error) 56362306a36Sopenharmony_ci return error; 56462306a36Sopenharmony_ci 56562306a36Sopenharmony_ci count = 0; 56662306a36Sopenharmony_ci } 56762306a36Sopenharmony_ci 56862306a36Sopenharmony_ci if (delta == 2) 56962306a36Sopenharmony_ci pcu->urb_out_buf[count++] = IMS_PCU_PROTOCOL_DLE; 57062306a36Sopenharmony_ci 57162306a36Sopenharmony_ci pcu->urb_out_buf[count++] = data[i]; 57262306a36Sopenharmony_ci csum += data[i]; 57362306a36Sopenharmony_ci } 57462306a36Sopenharmony_ci 57562306a36Sopenharmony_ci csum = 1 + ~csum; 57662306a36Sopenharmony_ci 57762306a36Sopenharmony_ci delta = ims_pcu_byte_needs_escape(csum) ? 3 : 2; 57862306a36Sopenharmony_ci if (count + delta >= pcu->max_out_size) { 57962306a36Sopenharmony_ci error = ims_pcu_send_cmd_chunk(pcu, command, ++chunk, count); 58062306a36Sopenharmony_ci if (error) 58162306a36Sopenharmony_ci return error; 58262306a36Sopenharmony_ci 58362306a36Sopenharmony_ci count = 0; 58462306a36Sopenharmony_ci } 58562306a36Sopenharmony_ci 58662306a36Sopenharmony_ci if (delta == 3) 58762306a36Sopenharmony_ci pcu->urb_out_buf[count++] = IMS_PCU_PROTOCOL_DLE; 58862306a36Sopenharmony_ci 58962306a36Sopenharmony_ci pcu->urb_out_buf[count++] = csum; 59062306a36Sopenharmony_ci pcu->urb_out_buf[count++] = IMS_PCU_PROTOCOL_ETX; 59162306a36Sopenharmony_ci 59262306a36Sopenharmony_ci return ims_pcu_send_cmd_chunk(pcu, command, ++chunk, count); 59362306a36Sopenharmony_ci} 59462306a36Sopenharmony_ci 59562306a36Sopenharmony_cistatic int __ims_pcu_execute_command(struct ims_pcu *pcu, 59662306a36Sopenharmony_ci u8 command, const void *data, size_t len, 59762306a36Sopenharmony_ci u8 expected_response, int response_time) 59862306a36Sopenharmony_ci{ 59962306a36Sopenharmony_ci int error; 60062306a36Sopenharmony_ci 60162306a36Sopenharmony_ci pcu->expected_response = expected_response; 60262306a36Sopenharmony_ci init_completion(&pcu->cmd_done); 60362306a36Sopenharmony_ci 60462306a36Sopenharmony_ci error = ims_pcu_send_command(pcu, command, data, len); 60562306a36Sopenharmony_ci if (error) 60662306a36Sopenharmony_ci return error; 60762306a36Sopenharmony_ci 60862306a36Sopenharmony_ci if (expected_response && 60962306a36Sopenharmony_ci !wait_for_completion_timeout(&pcu->cmd_done, 61062306a36Sopenharmony_ci msecs_to_jiffies(response_time))) { 61162306a36Sopenharmony_ci dev_dbg(pcu->dev, "Command 0x%02x timed out\n", command); 61262306a36Sopenharmony_ci return -ETIMEDOUT; 61362306a36Sopenharmony_ci } 61462306a36Sopenharmony_ci 61562306a36Sopenharmony_ci return 0; 61662306a36Sopenharmony_ci} 61762306a36Sopenharmony_ci 61862306a36Sopenharmony_ci#define ims_pcu_execute_command(pcu, code, data, len) \ 61962306a36Sopenharmony_ci __ims_pcu_execute_command(pcu, \ 62062306a36Sopenharmony_ci IMS_PCU_CMD_##code, data, len, \ 62162306a36Sopenharmony_ci IMS_PCU_RSP_##code, \ 62262306a36Sopenharmony_ci IMS_PCU_CMD_RESPONSE_TIMEOUT) 62362306a36Sopenharmony_ci 62462306a36Sopenharmony_ci#define ims_pcu_execute_query(pcu, code) \ 62562306a36Sopenharmony_ci ims_pcu_execute_command(pcu, code, NULL, 0) 62662306a36Sopenharmony_ci 62762306a36Sopenharmony_ci/* Bootloader commands */ 62862306a36Sopenharmony_ci#define IMS_PCU_BL_CMD_QUERY_DEVICE 0xa1 62962306a36Sopenharmony_ci#define IMS_PCU_BL_CMD_UNLOCK_CONFIG 0xa2 63062306a36Sopenharmony_ci#define IMS_PCU_BL_CMD_ERASE_APP 0xa3 63162306a36Sopenharmony_ci#define IMS_PCU_BL_CMD_PROGRAM_DEVICE 0xa4 63262306a36Sopenharmony_ci#define IMS_PCU_BL_CMD_PROGRAM_COMPLETE 0xa5 63362306a36Sopenharmony_ci#define IMS_PCU_BL_CMD_READ_APP 0xa6 63462306a36Sopenharmony_ci#define IMS_PCU_BL_CMD_RESET_DEVICE 0xa7 63562306a36Sopenharmony_ci#define IMS_PCU_BL_CMD_LAUNCH_APP 0xa8 63662306a36Sopenharmony_ci 63762306a36Sopenharmony_ci/* Bootloader commands */ 63862306a36Sopenharmony_ci#define IMS_PCU_BL_RSP_QUERY_DEVICE 0xc1 63962306a36Sopenharmony_ci#define IMS_PCU_BL_RSP_UNLOCK_CONFIG 0xc2 64062306a36Sopenharmony_ci#define IMS_PCU_BL_RSP_ERASE_APP 0xc3 64162306a36Sopenharmony_ci#define IMS_PCU_BL_RSP_PROGRAM_DEVICE 0xc4 64262306a36Sopenharmony_ci#define IMS_PCU_BL_RSP_PROGRAM_COMPLETE 0xc5 64362306a36Sopenharmony_ci#define IMS_PCU_BL_RSP_READ_APP 0xc6 64462306a36Sopenharmony_ci#define IMS_PCU_BL_RSP_RESET_DEVICE 0 /* originally 0xa7 */ 64562306a36Sopenharmony_ci#define IMS_PCU_BL_RSP_LAUNCH_APP 0 /* originally 0xa8 */ 64662306a36Sopenharmony_ci 64762306a36Sopenharmony_ci#define IMS_PCU_BL_DATA_OFFSET 3 64862306a36Sopenharmony_ci 64962306a36Sopenharmony_cistatic int __ims_pcu_execute_bl_command(struct ims_pcu *pcu, 65062306a36Sopenharmony_ci u8 command, const void *data, size_t len, 65162306a36Sopenharmony_ci u8 expected_response, int response_time) 65262306a36Sopenharmony_ci{ 65362306a36Sopenharmony_ci int error; 65462306a36Sopenharmony_ci 65562306a36Sopenharmony_ci pcu->cmd_buf[0] = command; 65662306a36Sopenharmony_ci if (data) 65762306a36Sopenharmony_ci memcpy(&pcu->cmd_buf[1], data, len); 65862306a36Sopenharmony_ci 65962306a36Sopenharmony_ci error = __ims_pcu_execute_command(pcu, 66062306a36Sopenharmony_ci IMS_PCU_CMD_BOOTLOADER, pcu->cmd_buf, len + 1, 66162306a36Sopenharmony_ci expected_response ? IMS_PCU_RSP_BOOTLOADER : 0, 66262306a36Sopenharmony_ci response_time); 66362306a36Sopenharmony_ci if (error) { 66462306a36Sopenharmony_ci dev_err(pcu->dev, 66562306a36Sopenharmony_ci "Failure when sending 0x%02x command to bootloader, error: %d\n", 66662306a36Sopenharmony_ci pcu->cmd_buf[0], error); 66762306a36Sopenharmony_ci return error; 66862306a36Sopenharmony_ci } 66962306a36Sopenharmony_ci 67062306a36Sopenharmony_ci if (expected_response && pcu->cmd_buf[2] != expected_response) { 67162306a36Sopenharmony_ci dev_err(pcu->dev, 67262306a36Sopenharmony_ci "Unexpected response from bootloader: 0x%02x, wanted 0x%02x\n", 67362306a36Sopenharmony_ci pcu->cmd_buf[2], expected_response); 67462306a36Sopenharmony_ci return -EINVAL; 67562306a36Sopenharmony_ci } 67662306a36Sopenharmony_ci 67762306a36Sopenharmony_ci return 0; 67862306a36Sopenharmony_ci} 67962306a36Sopenharmony_ci 68062306a36Sopenharmony_ci#define ims_pcu_execute_bl_command(pcu, code, data, len, timeout) \ 68162306a36Sopenharmony_ci __ims_pcu_execute_bl_command(pcu, \ 68262306a36Sopenharmony_ci IMS_PCU_BL_CMD_##code, data, len, \ 68362306a36Sopenharmony_ci IMS_PCU_BL_RSP_##code, timeout) \ 68462306a36Sopenharmony_ci 68562306a36Sopenharmony_ci#define IMS_PCU_INFO_PART_OFFSET 2 68662306a36Sopenharmony_ci#define IMS_PCU_INFO_DOM_OFFSET 17 68762306a36Sopenharmony_ci#define IMS_PCU_INFO_SERIAL_OFFSET 25 68862306a36Sopenharmony_ci 68962306a36Sopenharmony_ci#define IMS_PCU_SET_INFO_SIZE 31 69062306a36Sopenharmony_ci 69162306a36Sopenharmony_cistatic int ims_pcu_get_info(struct ims_pcu *pcu) 69262306a36Sopenharmony_ci{ 69362306a36Sopenharmony_ci int error; 69462306a36Sopenharmony_ci 69562306a36Sopenharmony_ci error = ims_pcu_execute_query(pcu, GET_INFO); 69662306a36Sopenharmony_ci if (error) { 69762306a36Sopenharmony_ci dev_err(pcu->dev, 69862306a36Sopenharmony_ci "GET_INFO command failed, error: %d\n", error); 69962306a36Sopenharmony_ci return error; 70062306a36Sopenharmony_ci } 70162306a36Sopenharmony_ci 70262306a36Sopenharmony_ci memcpy(pcu->part_number, 70362306a36Sopenharmony_ci &pcu->cmd_buf[IMS_PCU_INFO_PART_OFFSET], 70462306a36Sopenharmony_ci sizeof(pcu->part_number)); 70562306a36Sopenharmony_ci memcpy(pcu->date_of_manufacturing, 70662306a36Sopenharmony_ci &pcu->cmd_buf[IMS_PCU_INFO_DOM_OFFSET], 70762306a36Sopenharmony_ci sizeof(pcu->date_of_manufacturing)); 70862306a36Sopenharmony_ci memcpy(pcu->serial_number, 70962306a36Sopenharmony_ci &pcu->cmd_buf[IMS_PCU_INFO_SERIAL_OFFSET], 71062306a36Sopenharmony_ci sizeof(pcu->serial_number)); 71162306a36Sopenharmony_ci 71262306a36Sopenharmony_ci return 0; 71362306a36Sopenharmony_ci} 71462306a36Sopenharmony_ci 71562306a36Sopenharmony_cistatic int ims_pcu_set_info(struct ims_pcu *pcu) 71662306a36Sopenharmony_ci{ 71762306a36Sopenharmony_ci int error; 71862306a36Sopenharmony_ci 71962306a36Sopenharmony_ci memcpy(&pcu->cmd_buf[IMS_PCU_INFO_PART_OFFSET], 72062306a36Sopenharmony_ci pcu->part_number, sizeof(pcu->part_number)); 72162306a36Sopenharmony_ci memcpy(&pcu->cmd_buf[IMS_PCU_INFO_DOM_OFFSET], 72262306a36Sopenharmony_ci pcu->date_of_manufacturing, sizeof(pcu->date_of_manufacturing)); 72362306a36Sopenharmony_ci memcpy(&pcu->cmd_buf[IMS_PCU_INFO_SERIAL_OFFSET], 72462306a36Sopenharmony_ci pcu->serial_number, sizeof(pcu->serial_number)); 72562306a36Sopenharmony_ci 72662306a36Sopenharmony_ci error = ims_pcu_execute_command(pcu, SET_INFO, 72762306a36Sopenharmony_ci &pcu->cmd_buf[IMS_PCU_DATA_OFFSET], 72862306a36Sopenharmony_ci IMS_PCU_SET_INFO_SIZE); 72962306a36Sopenharmony_ci if (error) { 73062306a36Sopenharmony_ci dev_err(pcu->dev, 73162306a36Sopenharmony_ci "Failed to update device information, error: %d\n", 73262306a36Sopenharmony_ci error); 73362306a36Sopenharmony_ci return error; 73462306a36Sopenharmony_ci } 73562306a36Sopenharmony_ci 73662306a36Sopenharmony_ci return 0; 73762306a36Sopenharmony_ci} 73862306a36Sopenharmony_ci 73962306a36Sopenharmony_cistatic int ims_pcu_switch_to_bootloader(struct ims_pcu *pcu) 74062306a36Sopenharmony_ci{ 74162306a36Sopenharmony_ci int error; 74262306a36Sopenharmony_ci 74362306a36Sopenharmony_ci /* Execute jump to the bootoloader */ 74462306a36Sopenharmony_ci error = ims_pcu_execute_command(pcu, JUMP_TO_BTLDR, NULL, 0); 74562306a36Sopenharmony_ci if (error) { 74662306a36Sopenharmony_ci dev_err(pcu->dev, 74762306a36Sopenharmony_ci "Failure when sending JUMP TO BOOTLOADER command, error: %d\n", 74862306a36Sopenharmony_ci error); 74962306a36Sopenharmony_ci return error; 75062306a36Sopenharmony_ci } 75162306a36Sopenharmony_ci 75262306a36Sopenharmony_ci return 0; 75362306a36Sopenharmony_ci} 75462306a36Sopenharmony_ci 75562306a36Sopenharmony_ci/********************************************************************* 75662306a36Sopenharmony_ci * Firmware Update handling * 75762306a36Sopenharmony_ci *********************************************************************/ 75862306a36Sopenharmony_ci 75962306a36Sopenharmony_ci#define IMS_PCU_FIRMWARE_NAME "imspcu.fw" 76062306a36Sopenharmony_ci 76162306a36Sopenharmony_cistruct ims_pcu_flash_fmt { 76262306a36Sopenharmony_ci __le32 addr; 76362306a36Sopenharmony_ci u8 len; 76462306a36Sopenharmony_ci u8 data[]; 76562306a36Sopenharmony_ci}; 76662306a36Sopenharmony_ci 76762306a36Sopenharmony_cistatic unsigned int ims_pcu_count_fw_records(const struct firmware *fw) 76862306a36Sopenharmony_ci{ 76962306a36Sopenharmony_ci const struct ihex_binrec *rec = (const struct ihex_binrec *)fw->data; 77062306a36Sopenharmony_ci unsigned int count = 0; 77162306a36Sopenharmony_ci 77262306a36Sopenharmony_ci while (rec) { 77362306a36Sopenharmony_ci count++; 77462306a36Sopenharmony_ci rec = ihex_next_binrec(rec); 77562306a36Sopenharmony_ci } 77662306a36Sopenharmony_ci 77762306a36Sopenharmony_ci return count; 77862306a36Sopenharmony_ci} 77962306a36Sopenharmony_ci 78062306a36Sopenharmony_cistatic int ims_pcu_verify_block(struct ims_pcu *pcu, 78162306a36Sopenharmony_ci u32 addr, u8 len, const u8 *data) 78262306a36Sopenharmony_ci{ 78362306a36Sopenharmony_ci struct ims_pcu_flash_fmt *fragment; 78462306a36Sopenharmony_ci int error; 78562306a36Sopenharmony_ci 78662306a36Sopenharmony_ci fragment = (void *)&pcu->cmd_buf[1]; 78762306a36Sopenharmony_ci put_unaligned_le32(addr, &fragment->addr); 78862306a36Sopenharmony_ci fragment->len = len; 78962306a36Sopenharmony_ci 79062306a36Sopenharmony_ci error = ims_pcu_execute_bl_command(pcu, READ_APP, NULL, 5, 79162306a36Sopenharmony_ci IMS_PCU_CMD_RESPONSE_TIMEOUT); 79262306a36Sopenharmony_ci if (error) { 79362306a36Sopenharmony_ci dev_err(pcu->dev, 79462306a36Sopenharmony_ci "Failed to retrieve block at 0x%08x, len %d, error: %d\n", 79562306a36Sopenharmony_ci addr, len, error); 79662306a36Sopenharmony_ci return error; 79762306a36Sopenharmony_ci } 79862306a36Sopenharmony_ci 79962306a36Sopenharmony_ci fragment = (void *)&pcu->cmd_buf[IMS_PCU_BL_DATA_OFFSET]; 80062306a36Sopenharmony_ci if (get_unaligned_le32(&fragment->addr) != addr || 80162306a36Sopenharmony_ci fragment->len != len) { 80262306a36Sopenharmony_ci dev_err(pcu->dev, 80362306a36Sopenharmony_ci "Wrong block when retrieving 0x%08x (0x%08x), len %d (%d)\n", 80462306a36Sopenharmony_ci addr, get_unaligned_le32(&fragment->addr), 80562306a36Sopenharmony_ci len, fragment->len); 80662306a36Sopenharmony_ci return -EINVAL; 80762306a36Sopenharmony_ci } 80862306a36Sopenharmony_ci 80962306a36Sopenharmony_ci if (memcmp(fragment->data, data, len)) { 81062306a36Sopenharmony_ci dev_err(pcu->dev, 81162306a36Sopenharmony_ci "Mismatch in block at 0x%08x, len %d\n", 81262306a36Sopenharmony_ci addr, len); 81362306a36Sopenharmony_ci return -EINVAL; 81462306a36Sopenharmony_ci } 81562306a36Sopenharmony_ci 81662306a36Sopenharmony_ci return 0; 81762306a36Sopenharmony_ci} 81862306a36Sopenharmony_ci 81962306a36Sopenharmony_cistatic int ims_pcu_flash_firmware(struct ims_pcu *pcu, 82062306a36Sopenharmony_ci const struct firmware *fw, 82162306a36Sopenharmony_ci unsigned int n_fw_records) 82262306a36Sopenharmony_ci{ 82362306a36Sopenharmony_ci const struct ihex_binrec *rec = (const struct ihex_binrec *)fw->data; 82462306a36Sopenharmony_ci struct ims_pcu_flash_fmt *fragment; 82562306a36Sopenharmony_ci unsigned int count = 0; 82662306a36Sopenharmony_ci u32 addr; 82762306a36Sopenharmony_ci u8 len; 82862306a36Sopenharmony_ci int error; 82962306a36Sopenharmony_ci 83062306a36Sopenharmony_ci error = ims_pcu_execute_bl_command(pcu, ERASE_APP, NULL, 0, 2000); 83162306a36Sopenharmony_ci if (error) { 83262306a36Sopenharmony_ci dev_err(pcu->dev, 83362306a36Sopenharmony_ci "Failed to erase application image, error: %d\n", 83462306a36Sopenharmony_ci error); 83562306a36Sopenharmony_ci return error; 83662306a36Sopenharmony_ci } 83762306a36Sopenharmony_ci 83862306a36Sopenharmony_ci while (rec) { 83962306a36Sopenharmony_ci /* 84062306a36Sopenharmony_ci * The firmware format is messed up for some reason. 84162306a36Sopenharmony_ci * The address twice that of what is needed for some 84262306a36Sopenharmony_ci * reason and we end up overwriting half of the data 84362306a36Sopenharmony_ci * with the next record. 84462306a36Sopenharmony_ci */ 84562306a36Sopenharmony_ci addr = be32_to_cpu(rec->addr) / 2; 84662306a36Sopenharmony_ci len = be16_to_cpu(rec->len); 84762306a36Sopenharmony_ci 84862306a36Sopenharmony_ci fragment = (void *)&pcu->cmd_buf[1]; 84962306a36Sopenharmony_ci put_unaligned_le32(addr, &fragment->addr); 85062306a36Sopenharmony_ci fragment->len = len; 85162306a36Sopenharmony_ci memcpy(fragment->data, rec->data, len); 85262306a36Sopenharmony_ci 85362306a36Sopenharmony_ci error = ims_pcu_execute_bl_command(pcu, PROGRAM_DEVICE, 85462306a36Sopenharmony_ci NULL, len + 5, 85562306a36Sopenharmony_ci IMS_PCU_CMD_RESPONSE_TIMEOUT); 85662306a36Sopenharmony_ci if (error) { 85762306a36Sopenharmony_ci dev_err(pcu->dev, 85862306a36Sopenharmony_ci "Failed to write block at 0x%08x, len %d, error: %d\n", 85962306a36Sopenharmony_ci addr, len, error); 86062306a36Sopenharmony_ci return error; 86162306a36Sopenharmony_ci } 86262306a36Sopenharmony_ci 86362306a36Sopenharmony_ci if (addr >= pcu->fw_start_addr && addr < pcu->fw_end_addr) { 86462306a36Sopenharmony_ci error = ims_pcu_verify_block(pcu, addr, len, rec->data); 86562306a36Sopenharmony_ci if (error) 86662306a36Sopenharmony_ci return error; 86762306a36Sopenharmony_ci } 86862306a36Sopenharmony_ci 86962306a36Sopenharmony_ci count++; 87062306a36Sopenharmony_ci pcu->update_firmware_status = (count * 100) / n_fw_records; 87162306a36Sopenharmony_ci 87262306a36Sopenharmony_ci rec = ihex_next_binrec(rec); 87362306a36Sopenharmony_ci } 87462306a36Sopenharmony_ci 87562306a36Sopenharmony_ci error = ims_pcu_execute_bl_command(pcu, PROGRAM_COMPLETE, 87662306a36Sopenharmony_ci NULL, 0, 2000); 87762306a36Sopenharmony_ci if (error) 87862306a36Sopenharmony_ci dev_err(pcu->dev, 87962306a36Sopenharmony_ci "Failed to send PROGRAM_COMPLETE, error: %d\n", 88062306a36Sopenharmony_ci error); 88162306a36Sopenharmony_ci 88262306a36Sopenharmony_ci return 0; 88362306a36Sopenharmony_ci} 88462306a36Sopenharmony_ci 88562306a36Sopenharmony_cistatic int ims_pcu_handle_firmware_update(struct ims_pcu *pcu, 88662306a36Sopenharmony_ci const struct firmware *fw) 88762306a36Sopenharmony_ci{ 88862306a36Sopenharmony_ci unsigned int n_fw_records; 88962306a36Sopenharmony_ci int retval; 89062306a36Sopenharmony_ci 89162306a36Sopenharmony_ci dev_info(pcu->dev, "Updating firmware %s, size: %zu\n", 89262306a36Sopenharmony_ci IMS_PCU_FIRMWARE_NAME, fw->size); 89362306a36Sopenharmony_ci 89462306a36Sopenharmony_ci n_fw_records = ims_pcu_count_fw_records(fw); 89562306a36Sopenharmony_ci 89662306a36Sopenharmony_ci retval = ims_pcu_flash_firmware(pcu, fw, n_fw_records); 89762306a36Sopenharmony_ci if (retval) 89862306a36Sopenharmony_ci goto out; 89962306a36Sopenharmony_ci 90062306a36Sopenharmony_ci retval = ims_pcu_execute_bl_command(pcu, LAUNCH_APP, NULL, 0, 0); 90162306a36Sopenharmony_ci if (retval) 90262306a36Sopenharmony_ci dev_err(pcu->dev, 90362306a36Sopenharmony_ci "Failed to start application image, error: %d\n", 90462306a36Sopenharmony_ci retval); 90562306a36Sopenharmony_ci 90662306a36Sopenharmony_ciout: 90762306a36Sopenharmony_ci pcu->update_firmware_status = retval; 90862306a36Sopenharmony_ci sysfs_notify(&pcu->dev->kobj, NULL, "update_firmware_status"); 90962306a36Sopenharmony_ci return retval; 91062306a36Sopenharmony_ci} 91162306a36Sopenharmony_ci 91262306a36Sopenharmony_cistatic void ims_pcu_process_async_firmware(const struct firmware *fw, 91362306a36Sopenharmony_ci void *context) 91462306a36Sopenharmony_ci{ 91562306a36Sopenharmony_ci struct ims_pcu *pcu = context; 91662306a36Sopenharmony_ci int error; 91762306a36Sopenharmony_ci 91862306a36Sopenharmony_ci if (!fw) { 91962306a36Sopenharmony_ci dev_err(pcu->dev, "Failed to get firmware %s\n", 92062306a36Sopenharmony_ci IMS_PCU_FIRMWARE_NAME); 92162306a36Sopenharmony_ci goto out; 92262306a36Sopenharmony_ci } 92362306a36Sopenharmony_ci 92462306a36Sopenharmony_ci error = ihex_validate_fw(fw); 92562306a36Sopenharmony_ci if (error) { 92662306a36Sopenharmony_ci dev_err(pcu->dev, "Firmware %s is invalid\n", 92762306a36Sopenharmony_ci IMS_PCU_FIRMWARE_NAME); 92862306a36Sopenharmony_ci goto out; 92962306a36Sopenharmony_ci } 93062306a36Sopenharmony_ci 93162306a36Sopenharmony_ci mutex_lock(&pcu->cmd_mutex); 93262306a36Sopenharmony_ci ims_pcu_handle_firmware_update(pcu, fw); 93362306a36Sopenharmony_ci mutex_unlock(&pcu->cmd_mutex); 93462306a36Sopenharmony_ci 93562306a36Sopenharmony_ci release_firmware(fw); 93662306a36Sopenharmony_ci 93762306a36Sopenharmony_ciout: 93862306a36Sopenharmony_ci complete(&pcu->async_firmware_done); 93962306a36Sopenharmony_ci} 94062306a36Sopenharmony_ci 94162306a36Sopenharmony_ci/********************************************************************* 94262306a36Sopenharmony_ci * Backlight LED device support * 94362306a36Sopenharmony_ci *********************************************************************/ 94462306a36Sopenharmony_ci 94562306a36Sopenharmony_ci#define IMS_PCU_MAX_BRIGHTNESS 31998 94662306a36Sopenharmony_ci 94762306a36Sopenharmony_cistatic int ims_pcu_backlight_set_brightness(struct led_classdev *cdev, 94862306a36Sopenharmony_ci enum led_brightness value) 94962306a36Sopenharmony_ci{ 95062306a36Sopenharmony_ci struct ims_pcu_backlight *backlight = 95162306a36Sopenharmony_ci container_of(cdev, struct ims_pcu_backlight, cdev); 95262306a36Sopenharmony_ci struct ims_pcu *pcu = 95362306a36Sopenharmony_ci container_of(backlight, struct ims_pcu, backlight); 95462306a36Sopenharmony_ci __le16 br_val = cpu_to_le16(value); 95562306a36Sopenharmony_ci int error; 95662306a36Sopenharmony_ci 95762306a36Sopenharmony_ci mutex_lock(&pcu->cmd_mutex); 95862306a36Sopenharmony_ci 95962306a36Sopenharmony_ci error = ims_pcu_execute_command(pcu, SET_BRIGHTNESS, 96062306a36Sopenharmony_ci &br_val, sizeof(br_val)); 96162306a36Sopenharmony_ci if (error && error != -ENODEV) 96262306a36Sopenharmony_ci dev_warn(pcu->dev, 96362306a36Sopenharmony_ci "Failed to set desired brightness %u, error: %d\n", 96462306a36Sopenharmony_ci value, error); 96562306a36Sopenharmony_ci 96662306a36Sopenharmony_ci mutex_unlock(&pcu->cmd_mutex); 96762306a36Sopenharmony_ci 96862306a36Sopenharmony_ci return error; 96962306a36Sopenharmony_ci} 97062306a36Sopenharmony_ci 97162306a36Sopenharmony_cistatic enum led_brightness 97262306a36Sopenharmony_ciims_pcu_backlight_get_brightness(struct led_classdev *cdev) 97362306a36Sopenharmony_ci{ 97462306a36Sopenharmony_ci struct ims_pcu_backlight *backlight = 97562306a36Sopenharmony_ci container_of(cdev, struct ims_pcu_backlight, cdev); 97662306a36Sopenharmony_ci struct ims_pcu *pcu = 97762306a36Sopenharmony_ci container_of(backlight, struct ims_pcu, backlight); 97862306a36Sopenharmony_ci int brightness; 97962306a36Sopenharmony_ci int error; 98062306a36Sopenharmony_ci 98162306a36Sopenharmony_ci mutex_lock(&pcu->cmd_mutex); 98262306a36Sopenharmony_ci 98362306a36Sopenharmony_ci error = ims_pcu_execute_query(pcu, GET_BRIGHTNESS); 98462306a36Sopenharmony_ci if (error) { 98562306a36Sopenharmony_ci dev_warn(pcu->dev, 98662306a36Sopenharmony_ci "Failed to get current brightness, error: %d\n", 98762306a36Sopenharmony_ci error); 98862306a36Sopenharmony_ci /* Assume the LED is OFF */ 98962306a36Sopenharmony_ci brightness = LED_OFF; 99062306a36Sopenharmony_ci } else { 99162306a36Sopenharmony_ci brightness = 99262306a36Sopenharmony_ci get_unaligned_le16(&pcu->cmd_buf[IMS_PCU_DATA_OFFSET]); 99362306a36Sopenharmony_ci } 99462306a36Sopenharmony_ci 99562306a36Sopenharmony_ci mutex_unlock(&pcu->cmd_mutex); 99662306a36Sopenharmony_ci 99762306a36Sopenharmony_ci return brightness; 99862306a36Sopenharmony_ci} 99962306a36Sopenharmony_ci 100062306a36Sopenharmony_cistatic int ims_pcu_setup_backlight(struct ims_pcu *pcu) 100162306a36Sopenharmony_ci{ 100262306a36Sopenharmony_ci struct ims_pcu_backlight *backlight = &pcu->backlight; 100362306a36Sopenharmony_ci int error; 100462306a36Sopenharmony_ci 100562306a36Sopenharmony_ci snprintf(backlight->name, sizeof(backlight->name), 100662306a36Sopenharmony_ci "pcu%d::kbd_backlight", pcu->device_no); 100762306a36Sopenharmony_ci 100862306a36Sopenharmony_ci backlight->cdev.name = backlight->name; 100962306a36Sopenharmony_ci backlight->cdev.max_brightness = IMS_PCU_MAX_BRIGHTNESS; 101062306a36Sopenharmony_ci backlight->cdev.brightness_get = ims_pcu_backlight_get_brightness; 101162306a36Sopenharmony_ci backlight->cdev.brightness_set_blocking = 101262306a36Sopenharmony_ci ims_pcu_backlight_set_brightness; 101362306a36Sopenharmony_ci 101462306a36Sopenharmony_ci error = led_classdev_register(pcu->dev, &backlight->cdev); 101562306a36Sopenharmony_ci if (error) { 101662306a36Sopenharmony_ci dev_err(pcu->dev, 101762306a36Sopenharmony_ci "Failed to register backlight LED device, error: %d\n", 101862306a36Sopenharmony_ci error); 101962306a36Sopenharmony_ci return error; 102062306a36Sopenharmony_ci } 102162306a36Sopenharmony_ci 102262306a36Sopenharmony_ci return 0; 102362306a36Sopenharmony_ci} 102462306a36Sopenharmony_ci 102562306a36Sopenharmony_cistatic void ims_pcu_destroy_backlight(struct ims_pcu *pcu) 102662306a36Sopenharmony_ci{ 102762306a36Sopenharmony_ci struct ims_pcu_backlight *backlight = &pcu->backlight; 102862306a36Sopenharmony_ci 102962306a36Sopenharmony_ci led_classdev_unregister(&backlight->cdev); 103062306a36Sopenharmony_ci} 103162306a36Sopenharmony_ci 103262306a36Sopenharmony_ci 103362306a36Sopenharmony_ci/********************************************************************* 103462306a36Sopenharmony_ci * Sysfs attributes handling * 103562306a36Sopenharmony_ci *********************************************************************/ 103662306a36Sopenharmony_ci 103762306a36Sopenharmony_cistruct ims_pcu_attribute { 103862306a36Sopenharmony_ci struct device_attribute dattr; 103962306a36Sopenharmony_ci size_t field_offset; 104062306a36Sopenharmony_ci int field_length; 104162306a36Sopenharmony_ci}; 104262306a36Sopenharmony_ci 104362306a36Sopenharmony_cistatic ssize_t ims_pcu_attribute_show(struct device *dev, 104462306a36Sopenharmony_ci struct device_attribute *dattr, 104562306a36Sopenharmony_ci char *buf) 104662306a36Sopenharmony_ci{ 104762306a36Sopenharmony_ci struct usb_interface *intf = to_usb_interface(dev); 104862306a36Sopenharmony_ci struct ims_pcu *pcu = usb_get_intfdata(intf); 104962306a36Sopenharmony_ci struct ims_pcu_attribute *attr = 105062306a36Sopenharmony_ci container_of(dattr, struct ims_pcu_attribute, dattr); 105162306a36Sopenharmony_ci char *field = (char *)pcu + attr->field_offset; 105262306a36Sopenharmony_ci 105362306a36Sopenharmony_ci return scnprintf(buf, PAGE_SIZE, "%.*s\n", attr->field_length, field); 105462306a36Sopenharmony_ci} 105562306a36Sopenharmony_ci 105662306a36Sopenharmony_cistatic ssize_t ims_pcu_attribute_store(struct device *dev, 105762306a36Sopenharmony_ci struct device_attribute *dattr, 105862306a36Sopenharmony_ci const char *buf, size_t count) 105962306a36Sopenharmony_ci{ 106062306a36Sopenharmony_ci 106162306a36Sopenharmony_ci struct usb_interface *intf = to_usb_interface(dev); 106262306a36Sopenharmony_ci struct ims_pcu *pcu = usb_get_intfdata(intf); 106362306a36Sopenharmony_ci struct ims_pcu_attribute *attr = 106462306a36Sopenharmony_ci container_of(dattr, struct ims_pcu_attribute, dattr); 106562306a36Sopenharmony_ci char *field = (char *)pcu + attr->field_offset; 106662306a36Sopenharmony_ci size_t data_len; 106762306a36Sopenharmony_ci int error; 106862306a36Sopenharmony_ci 106962306a36Sopenharmony_ci if (count > attr->field_length) 107062306a36Sopenharmony_ci return -EINVAL; 107162306a36Sopenharmony_ci 107262306a36Sopenharmony_ci data_len = strnlen(buf, attr->field_length); 107362306a36Sopenharmony_ci if (data_len > attr->field_length) 107462306a36Sopenharmony_ci return -EINVAL; 107562306a36Sopenharmony_ci 107662306a36Sopenharmony_ci error = mutex_lock_interruptible(&pcu->cmd_mutex); 107762306a36Sopenharmony_ci if (error) 107862306a36Sopenharmony_ci return error; 107962306a36Sopenharmony_ci 108062306a36Sopenharmony_ci memset(field, 0, attr->field_length); 108162306a36Sopenharmony_ci memcpy(field, buf, data_len); 108262306a36Sopenharmony_ci 108362306a36Sopenharmony_ci error = ims_pcu_set_info(pcu); 108462306a36Sopenharmony_ci 108562306a36Sopenharmony_ci /* 108662306a36Sopenharmony_ci * Even if update failed, let's fetch the info again as we just 108762306a36Sopenharmony_ci * clobbered one of the fields. 108862306a36Sopenharmony_ci */ 108962306a36Sopenharmony_ci ims_pcu_get_info(pcu); 109062306a36Sopenharmony_ci 109162306a36Sopenharmony_ci mutex_unlock(&pcu->cmd_mutex); 109262306a36Sopenharmony_ci 109362306a36Sopenharmony_ci return error < 0 ? error : count; 109462306a36Sopenharmony_ci} 109562306a36Sopenharmony_ci 109662306a36Sopenharmony_ci#define IMS_PCU_ATTR(_field, _mode) \ 109762306a36Sopenharmony_cistruct ims_pcu_attribute ims_pcu_attr_##_field = { \ 109862306a36Sopenharmony_ci .dattr = __ATTR(_field, _mode, \ 109962306a36Sopenharmony_ci ims_pcu_attribute_show, \ 110062306a36Sopenharmony_ci ims_pcu_attribute_store), \ 110162306a36Sopenharmony_ci .field_offset = offsetof(struct ims_pcu, _field), \ 110262306a36Sopenharmony_ci .field_length = sizeof(((struct ims_pcu *)NULL)->_field), \ 110362306a36Sopenharmony_ci} 110462306a36Sopenharmony_ci 110562306a36Sopenharmony_ci#define IMS_PCU_RO_ATTR(_field) \ 110662306a36Sopenharmony_ci IMS_PCU_ATTR(_field, S_IRUGO) 110762306a36Sopenharmony_ci#define IMS_PCU_RW_ATTR(_field) \ 110862306a36Sopenharmony_ci IMS_PCU_ATTR(_field, S_IRUGO | S_IWUSR) 110962306a36Sopenharmony_ci 111062306a36Sopenharmony_cistatic IMS_PCU_RW_ATTR(part_number); 111162306a36Sopenharmony_cistatic IMS_PCU_RW_ATTR(serial_number); 111262306a36Sopenharmony_cistatic IMS_PCU_RW_ATTR(date_of_manufacturing); 111362306a36Sopenharmony_ci 111462306a36Sopenharmony_cistatic IMS_PCU_RO_ATTR(fw_version); 111562306a36Sopenharmony_cistatic IMS_PCU_RO_ATTR(bl_version); 111662306a36Sopenharmony_cistatic IMS_PCU_RO_ATTR(reset_reason); 111762306a36Sopenharmony_ci 111862306a36Sopenharmony_cistatic ssize_t ims_pcu_reset_device(struct device *dev, 111962306a36Sopenharmony_ci struct device_attribute *dattr, 112062306a36Sopenharmony_ci const char *buf, size_t count) 112162306a36Sopenharmony_ci{ 112262306a36Sopenharmony_ci static const u8 reset_byte = 1; 112362306a36Sopenharmony_ci struct usb_interface *intf = to_usb_interface(dev); 112462306a36Sopenharmony_ci struct ims_pcu *pcu = usb_get_intfdata(intf); 112562306a36Sopenharmony_ci int value; 112662306a36Sopenharmony_ci int error; 112762306a36Sopenharmony_ci 112862306a36Sopenharmony_ci error = kstrtoint(buf, 0, &value); 112962306a36Sopenharmony_ci if (error) 113062306a36Sopenharmony_ci return error; 113162306a36Sopenharmony_ci 113262306a36Sopenharmony_ci if (value != 1) 113362306a36Sopenharmony_ci return -EINVAL; 113462306a36Sopenharmony_ci 113562306a36Sopenharmony_ci dev_info(pcu->dev, "Attempting to reset device\n"); 113662306a36Sopenharmony_ci 113762306a36Sopenharmony_ci error = ims_pcu_execute_command(pcu, PCU_RESET, &reset_byte, 1); 113862306a36Sopenharmony_ci if (error) { 113962306a36Sopenharmony_ci dev_info(pcu->dev, 114062306a36Sopenharmony_ci "Failed to reset device, error: %d\n", 114162306a36Sopenharmony_ci error); 114262306a36Sopenharmony_ci return error; 114362306a36Sopenharmony_ci } 114462306a36Sopenharmony_ci 114562306a36Sopenharmony_ci return count; 114662306a36Sopenharmony_ci} 114762306a36Sopenharmony_ci 114862306a36Sopenharmony_cistatic DEVICE_ATTR(reset_device, S_IWUSR, NULL, ims_pcu_reset_device); 114962306a36Sopenharmony_ci 115062306a36Sopenharmony_cistatic ssize_t ims_pcu_update_firmware_store(struct device *dev, 115162306a36Sopenharmony_ci struct device_attribute *dattr, 115262306a36Sopenharmony_ci const char *buf, size_t count) 115362306a36Sopenharmony_ci{ 115462306a36Sopenharmony_ci struct usb_interface *intf = to_usb_interface(dev); 115562306a36Sopenharmony_ci struct ims_pcu *pcu = usb_get_intfdata(intf); 115662306a36Sopenharmony_ci const struct firmware *fw = NULL; 115762306a36Sopenharmony_ci int value; 115862306a36Sopenharmony_ci int error; 115962306a36Sopenharmony_ci 116062306a36Sopenharmony_ci error = kstrtoint(buf, 0, &value); 116162306a36Sopenharmony_ci if (error) 116262306a36Sopenharmony_ci return error; 116362306a36Sopenharmony_ci 116462306a36Sopenharmony_ci if (value != 1) 116562306a36Sopenharmony_ci return -EINVAL; 116662306a36Sopenharmony_ci 116762306a36Sopenharmony_ci error = mutex_lock_interruptible(&pcu->cmd_mutex); 116862306a36Sopenharmony_ci if (error) 116962306a36Sopenharmony_ci return error; 117062306a36Sopenharmony_ci 117162306a36Sopenharmony_ci error = request_ihex_firmware(&fw, IMS_PCU_FIRMWARE_NAME, pcu->dev); 117262306a36Sopenharmony_ci if (error) { 117362306a36Sopenharmony_ci dev_err(pcu->dev, "Failed to request firmware %s, error: %d\n", 117462306a36Sopenharmony_ci IMS_PCU_FIRMWARE_NAME, error); 117562306a36Sopenharmony_ci goto out; 117662306a36Sopenharmony_ci } 117762306a36Sopenharmony_ci 117862306a36Sopenharmony_ci /* 117962306a36Sopenharmony_ci * If we are already in bootloader mode we can proceed with 118062306a36Sopenharmony_ci * flashing the firmware. 118162306a36Sopenharmony_ci * 118262306a36Sopenharmony_ci * If we are in application mode, then we need to switch into 118362306a36Sopenharmony_ci * bootloader mode, which will cause the device to disconnect 118462306a36Sopenharmony_ci * and reconnect as different device. 118562306a36Sopenharmony_ci */ 118662306a36Sopenharmony_ci if (pcu->bootloader_mode) 118762306a36Sopenharmony_ci error = ims_pcu_handle_firmware_update(pcu, fw); 118862306a36Sopenharmony_ci else 118962306a36Sopenharmony_ci error = ims_pcu_switch_to_bootloader(pcu); 119062306a36Sopenharmony_ci 119162306a36Sopenharmony_ci release_firmware(fw); 119262306a36Sopenharmony_ci 119362306a36Sopenharmony_ciout: 119462306a36Sopenharmony_ci mutex_unlock(&pcu->cmd_mutex); 119562306a36Sopenharmony_ci return error ?: count; 119662306a36Sopenharmony_ci} 119762306a36Sopenharmony_ci 119862306a36Sopenharmony_cistatic DEVICE_ATTR(update_firmware, S_IWUSR, 119962306a36Sopenharmony_ci NULL, ims_pcu_update_firmware_store); 120062306a36Sopenharmony_ci 120162306a36Sopenharmony_cistatic ssize_t 120262306a36Sopenharmony_ciims_pcu_update_firmware_status_show(struct device *dev, 120362306a36Sopenharmony_ci struct device_attribute *dattr, 120462306a36Sopenharmony_ci char *buf) 120562306a36Sopenharmony_ci{ 120662306a36Sopenharmony_ci struct usb_interface *intf = to_usb_interface(dev); 120762306a36Sopenharmony_ci struct ims_pcu *pcu = usb_get_intfdata(intf); 120862306a36Sopenharmony_ci 120962306a36Sopenharmony_ci return scnprintf(buf, PAGE_SIZE, "%d\n", pcu->update_firmware_status); 121062306a36Sopenharmony_ci} 121162306a36Sopenharmony_ci 121262306a36Sopenharmony_cistatic DEVICE_ATTR(update_firmware_status, S_IRUGO, 121362306a36Sopenharmony_ci ims_pcu_update_firmware_status_show, NULL); 121462306a36Sopenharmony_ci 121562306a36Sopenharmony_cistatic struct attribute *ims_pcu_attrs[] = { 121662306a36Sopenharmony_ci &ims_pcu_attr_part_number.dattr.attr, 121762306a36Sopenharmony_ci &ims_pcu_attr_serial_number.dattr.attr, 121862306a36Sopenharmony_ci &ims_pcu_attr_date_of_manufacturing.dattr.attr, 121962306a36Sopenharmony_ci &ims_pcu_attr_fw_version.dattr.attr, 122062306a36Sopenharmony_ci &ims_pcu_attr_bl_version.dattr.attr, 122162306a36Sopenharmony_ci &ims_pcu_attr_reset_reason.dattr.attr, 122262306a36Sopenharmony_ci &dev_attr_reset_device.attr, 122362306a36Sopenharmony_ci &dev_attr_update_firmware.attr, 122462306a36Sopenharmony_ci &dev_attr_update_firmware_status.attr, 122562306a36Sopenharmony_ci NULL 122662306a36Sopenharmony_ci}; 122762306a36Sopenharmony_ci 122862306a36Sopenharmony_cistatic umode_t ims_pcu_is_attr_visible(struct kobject *kobj, 122962306a36Sopenharmony_ci struct attribute *attr, int n) 123062306a36Sopenharmony_ci{ 123162306a36Sopenharmony_ci struct device *dev = kobj_to_dev(kobj); 123262306a36Sopenharmony_ci struct usb_interface *intf = to_usb_interface(dev); 123362306a36Sopenharmony_ci struct ims_pcu *pcu = usb_get_intfdata(intf); 123462306a36Sopenharmony_ci umode_t mode = attr->mode; 123562306a36Sopenharmony_ci 123662306a36Sopenharmony_ci if (pcu->bootloader_mode) { 123762306a36Sopenharmony_ci if (attr != &dev_attr_update_firmware_status.attr && 123862306a36Sopenharmony_ci attr != &dev_attr_update_firmware.attr && 123962306a36Sopenharmony_ci attr != &dev_attr_reset_device.attr) { 124062306a36Sopenharmony_ci mode = 0; 124162306a36Sopenharmony_ci } 124262306a36Sopenharmony_ci } else { 124362306a36Sopenharmony_ci if (attr == &dev_attr_update_firmware_status.attr) 124462306a36Sopenharmony_ci mode = 0; 124562306a36Sopenharmony_ci } 124662306a36Sopenharmony_ci 124762306a36Sopenharmony_ci return mode; 124862306a36Sopenharmony_ci} 124962306a36Sopenharmony_ci 125062306a36Sopenharmony_cistatic const struct attribute_group ims_pcu_attr_group = { 125162306a36Sopenharmony_ci .is_visible = ims_pcu_is_attr_visible, 125262306a36Sopenharmony_ci .attrs = ims_pcu_attrs, 125362306a36Sopenharmony_ci}; 125462306a36Sopenharmony_ci 125562306a36Sopenharmony_ci/* Support for a separate OFN attribute group */ 125662306a36Sopenharmony_ci 125762306a36Sopenharmony_ci#define OFN_REG_RESULT_OFFSET 2 125862306a36Sopenharmony_ci 125962306a36Sopenharmony_cistatic int ims_pcu_read_ofn_config(struct ims_pcu *pcu, u8 addr, u8 *data) 126062306a36Sopenharmony_ci{ 126162306a36Sopenharmony_ci int error; 126262306a36Sopenharmony_ci s16 result; 126362306a36Sopenharmony_ci 126462306a36Sopenharmony_ci error = ims_pcu_execute_command(pcu, OFN_GET_CONFIG, 126562306a36Sopenharmony_ci &addr, sizeof(addr)); 126662306a36Sopenharmony_ci if (error) 126762306a36Sopenharmony_ci return error; 126862306a36Sopenharmony_ci 126962306a36Sopenharmony_ci result = (s16)get_unaligned_le16(pcu->cmd_buf + OFN_REG_RESULT_OFFSET); 127062306a36Sopenharmony_ci if (result < 0) 127162306a36Sopenharmony_ci return -EIO; 127262306a36Sopenharmony_ci 127362306a36Sopenharmony_ci /* We only need LSB */ 127462306a36Sopenharmony_ci *data = pcu->cmd_buf[OFN_REG_RESULT_OFFSET]; 127562306a36Sopenharmony_ci return 0; 127662306a36Sopenharmony_ci} 127762306a36Sopenharmony_ci 127862306a36Sopenharmony_cistatic int ims_pcu_write_ofn_config(struct ims_pcu *pcu, u8 addr, u8 data) 127962306a36Sopenharmony_ci{ 128062306a36Sopenharmony_ci u8 buffer[] = { addr, data }; 128162306a36Sopenharmony_ci int error; 128262306a36Sopenharmony_ci s16 result; 128362306a36Sopenharmony_ci 128462306a36Sopenharmony_ci error = ims_pcu_execute_command(pcu, OFN_SET_CONFIG, 128562306a36Sopenharmony_ci &buffer, sizeof(buffer)); 128662306a36Sopenharmony_ci if (error) 128762306a36Sopenharmony_ci return error; 128862306a36Sopenharmony_ci 128962306a36Sopenharmony_ci result = (s16)get_unaligned_le16(pcu->cmd_buf + OFN_REG_RESULT_OFFSET); 129062306a36Sopenharmony_ci if (result < 0) 129162306a36Sopenharmony_ci return -EIO; 129262306a36Sopenharmony_ci 129362306a36Sopenharmony_ci return 0; 129462306a36Sopenharmony_ci} 129562306a36Sopenharmony_ci 129662306a36Sopenharmony_cistatic ssize_t ims_pcu_ofn_reg_data_show(struct device *dev, 129762306a36Sopenharmony_ci struct device_attribute *dattr, 129862306a36Sopenharmony_ci char *buf) 129962306a36Sopenharmony_ci{ 130062306a36Sopenharmony_ci struct usb_interface *intf = to_usb_interface(dev); 130162306a36Sopenharmony_ci struct ims_pcu *pcu = usb_get_intfdata(intf); 130262306a36Sopenharmony_ci int error; 130362306a36Sopenharmony_ci u8 data; 130462306a36Sopenharmony_ci 130562306a36Sopenharmony_ci mutex_lock(&pcu->cmd_mutex); 130662306a36Sopenharmony_ci error = ims_pcu_read_ofn_config(pcu, pcu->ofn_reg_addr, &data); 130762306a36Sopenharmony_ci mutex_unlock(&pcu->cmd_mutex); 130862306a36Sopenharmony_ci 130962306a36Sopenharmony_ci if (error) 131062306a36Sopenharmony_ci return error; 131162306a36Sopenharmony_ci 131262306a36Sopenharmony_ci return scnprintf(buf, PAGE_SIZE, "%x\n", data); 131362306a36Sopenharmony_ci} 131462306a36Sopenharmony_ci 131562306a36Sopenharmony_cistatic ssize_t ims_pcu_ofn_reg_data_store(struct device *dev, 131662306a36Sopenharmony_ci struct device_attribute *dattr, 131762306a36Sopenharmony_ci const char *buf, size_t count) 131862306a36Sopenharmony_ci{ 131962306a36Sopenharmony_ci struct usb_interface *intf = to_usb_interface(dev); 132062306a36Sopenharmony_ci struct ims_pcu *pcu = usb_get_intfdata(intf); 132162306a36Sopenharmony_ci int error; 132262306a36Sopenharmony_ci u8 value; 132362306a36Sopenharmony_ci 132462306a36Sopenharmony_ci error = kstrtou8(buf, 0, &value); 132562306a36Sopenharmony_ci if (error) 132662306a36Sopenharmony_ci return error; 132762306a36Sopenharmony_ci 132862306a36Sopenharmony_ci mutex_lock(&pcu->cmd_mutex); 132962306a36Sopenharmony_ci error = ims_pcu_write_ofn_config(pcu, pcu->ofn_reg_addr, value); 133062306a36Sopenharmony_ci mutex_unlock(&pcu->cmd_mutex); 133162306a36Sopenharmony_ci 133262306a36Sopenharmony_ci return error ?: count; 133362306a36Sopenharmony_ci} 133462306a36Sopenharmony_ci 133562306a36Sopenharmony_cistatic DEVICE_ATTR(reg_data, S_IRUGO | S_IWUSR, 133662306a36Sopenharmony_ci ims_pcu_ofn_reg_data_show, ims_pcu_ofn_reg_data_store); 133762306a36Sopenharmony_ci 133862306a36Sopenharmony_cistatic ssize_t ims_pcu_ofn_reg_addr_show(struct device *dev, 133962306a36Sopenharmony_ci struct device_attribute *dattr, 134062306a36Sopenharmony_ci char *buf) 134162306a36Sopenharmony_ci{ 134262306a36Sopenharmony_ci struct usb_interface *intf = to_usb_interface(dev); 134362306a36Sopenharmony_ci struct ims_pcu *pcu = usb_get_intfdata(intf); 134462306a36Sopenharmony_ci int error; 134562306a36Sopenharmony_ci 134662306a36Sopenharmony_ci mutex_lock(&pcu->cmd_mutex); 134762306a36Sopenharmony_ci error = scnprintf(buf, PAGE_SIZE, "%x\n", pcu->ofn_reg_addr); 134862306a36Sopenharmony_ci mutex_unlock(&pcu->cmd_mutex); 134962306a36Sopenharmony_ci 135062306a36Sopenharmony_ci return error; 135162306a36Sopenharmony_ci} 135262306a36Sopenharmony_ci 135362306a36Sopenharmony_cistatic ssize_t ims_pcu_ofn_reg_addr_store(struct device *dev, 135462306a36Sopenharmony_ci struct device_attribute *dattr, 135562306a36Sopenharmony_ci const char *buf, size_t count) 135662306a36Sopenharmony_ci{ 135762306a36Sopenharmony_ci struct usb_interface *intf = to_usb_interface(dev); 135862306a36Sopenharmony_ci struct ims_pcu *pcu = usb_get_intfdata(intf); 135962306a36Sopenharmony_ci int error; 136062306a36Sopenharmony_ci u8 value; 136162306a36Sopenharmony_ci 136262306a36Sopenharmony_ci error = kstrtou8(buf, 0, &value); 136362306a36Sopenharmony_ci if (error) 136462306a36Sopenharmony_ci return error; 136562306a36Sopenharmony_ci 136662306a36Sopenharmony_ci mutex_lock(&pcu->cmd_mutex); 136762306a36Sopenharmony_ci pcu->ofn_reg_addr = value; 136862306a36Sopenharmony_ci mutex_unlock(&pcu->cmd_mutex); 136962306a36Sopenharmony_ci 137062306a36Sopenharmony_ci return count; 137162306a36Sopenharmony_ci} 137262306a36Sopenharmony_ci 137362306a36Sopenharmony_cistatic DEVICE_ATTR(reg_addr, S_IRUGO | S_IWUSR, 137462306a36Sopenharmony_ci ims_pcu_ofn_reg_addr_show, ims_pcu_ofn_reg_addr_store); 137562306a36Sopenharmony_ci 137662306a36Sopenharmony_cistruct ims_pcu_ofn_bit_attribute { 137762306a36Sopenharmony_ci struct device_attribute dattr; 137862306a36Sopenharmony_ci u8 addr; 137962306a36Sopenharmony_ci u8 nr; 138062306a36Sopenharmony_ci}; 138162306a36Sopenharmony_ci 138262306a36Sopenharmony_cistatic ssize_t ims_pcu_ofn_bit_show(struct device *dev, 138362306a36Sopenharmony_ci struct device_attribute *dattr, 138462306a36Sopenharmony_ci char *buf) 138562306a36Sopenharmony_ci{ 138662306a36Sopenharmony_ci struct usb_interface *intf = to_usb_interface(dev); 138762306a36Sopenharmony_ci struct ims_pcu *pcu = usb_get_intfdata(intf); 138862306a36Sopenharmony_ci struct ims_pcu_ofn_bit_attribute *attr = 138962306a36Sopenharmony_ci container_of(dattr, struct ims_pcu_ofn_bit_attribute, dattr); 139062306a36Sopenharmony_ci int error; 139162306a36Sopenharmony_ci u8 data; 139262306a36Sopenharmony_ci 139362306a36Sopenharmony_ci mutex_lock(&pcu->cmd_mutex); 139462306a36Sopenharmony_ci error = ims_pcu_read_ofn_config(pcu, attr->addr, &data); 139562306a36Sopenharmony_ci mutex_unlock(&pcu->cmd_mutex); 139662306a36Sopenharmony_ci 139762306a36Sopenharmony_ci if (error) 139862306a36Sopenharmony_ci return error; 139962306a36Sopenharmony_ci 140062306a36Sopenharmony_ci return scnprintf(buf, PAGE_SIZE, "%d\n", !!(data & (1 << attr->nr))); 140162306a36Sopenharmony_ci} 140262306a36Sopenharmony_ci 140362306a36Sopenharmony_cistatic ssize_t ims_pcu_ofn_bit_store(struct device *dev, 140462306a36Sopenharmony_ci struct device_attribute *dattr, 140562306a36Sopenharmony_ci const char *buf, size_t count) 140662306a36Sopenharmony_ci{ 140762306a36Sopenharmony_ci struct usb_interface *intf = to_usb_interface(dev); 140862306a36Sopenharmony_ci struct ims_pcu *pcu = usb_get_intfdata(intf); 140962306a36Sopenharmony_ci struct ims_pcu_ofn_bit_attribute *attr = 141062306a36Sopenharmony_ci container_of(dattr, struct ims_pcu_ofn_bit_attribute, dattr); 141162306a36Sopenharmony_ci int error; 141262306a36Sopenharmony_ci int value; 141362306a36Sopenharmony_ci u8 data; 141462306a36Sopenharmony_ci 141562306a36Sopenharmony_ci error = kstrtoint(buf, 0, &value); 141662306a36Sopenharmony_ci if (error) 141762306a36Sopenharmony_ci return error; 141862306a36Sopenharmony_ci 141962306a36Sopenharmony_ci if (value > 1) 142062306a36Sopenharmony_ci return -EINVAL; 142162306a36Sopenharmony_ci 142262306a36Sopenharmony_ci mutex_lock(&pcu->cmd_mutex); 142362306a36Sopenharmony_ci 142462306a36Sopenharmony_ci error = ims_pcu_read_ofn_config(pcu, attr->addr, &data); 142562306a36Sopenharmony_ci if (!error) { 142662306a36Sopenharmony_ci if (value) 142762306a36Sopenharmony_ci data |= 1U << attr->nr; 142862306a36Sopenharmony_ci else 142962306a36Sopenharmony_ci data &= ~(1U << attr->nr); 143062306a36Sopenharmony_ci 143162306a36Sopenharmony_ci error = ims_pcu_write_ofn_config(pcu, attr->addr, data); 143262306a36Sopenharmony_ci } 143362306a36Sopenharmony_ci 143462306a36Sopenharmony_ci mutex_unlock(&pcu->cmd_mutex); 143562306a36Sopenharmony_ci 143662306a36Sopenharmony_ci return error ?: count; 143762306a36Sopenharmony_ci} 143862306a36Sopenharmony_ci 143962306a36Sopenharmony_ci#define IMS_PCU_OFN_BIT_ATTR(_field, _addr, _nr) \ 144062306a36Sopenharmony_cistruct ims_pcu_ofn_bit_attribute ims_pcu_ofn_attr_##_field = { \ 144162306a36Sopenharmony_ci .dattr = __ATTR(_field, S_IWUSR | S_IRUGO, \ 144262306a36Sopenharmony_ci ims_pcu_ofn_bit_show, ims_pcu_ofn_bit_store), \ 144362306a36Sopenharmony_ci .addr = _addr, \ 144462306a36Sopenharmony_ci .nr = _nr, \ 144562306a36Sopenharmony_ci} 144662306a36Sopenharmony_ci 144762306a36Sopenharmony_cistatic IMS_PCU_OFN_BIT_ATTR(engine_enable, 0x60, 7); 144862306a36Sopenharmony_cistatic IMS_PCU_OFN_BIT_ATTR(speed_enable, 0x60, 6); 144962306a36Sopenharmony_cistatic IMS_PCU_OFN_BIT_ATTR(assert_enable, 0x60, 5); 145062306a36Sopenharmony_cistatic IMS_PCU_OFN_BIT_ATTR(xyquant_enable, 0x60, 4); 145162306a36Sopenharmony_cistatic IMS_PCU_OFN_BIT_ATTR(xyscale_enable, 0x60, 1); 145262306a36Sopenharmony_ci 145362306a36Sopenharmony_cistatic IMS_PCU_OFN_BIT_ATTR(scale_x2, 0x63, 6); 145462306a36Sopenharmony_cistatic IMS_PCU_OFN_BIT_ATTR(scale_y2, 0x63, 7); 145562306a36Sopenharmony_ci 145662306a36Sopenharmony_cistatic struct attribute *ims_pcu_ofn_attrs[] = { 145762306a36Sopenharmony_ci &dev_attr_reg_data.attr, 145862306a36Sopenharmony_ci &dev_attr_reg_addr.attr, 145962306a36Sopenharmony_ci &ims_pcu_ofn_attr_engine_enable.dattr.attr, 146062306a36Sopenharmony_ci &ims_pcu_ofn_attr_speed_enable.dattr.attr, 146162306a36Sopenharmony_ci &ims_pcu_ofn_attr_assert_enable.dattr.attr, 146262306a36Sopenharmony_ci &ims_pcu_ofn_attr_xyquant_enable.dattr.attr, 146362306a36Sopenharmony_ci &ims_pcu_ofn_attr_xyscale_enable.dattr.attr, 146462306a36Sopenharmony_ci &ims_pcu_ofn_attr_scale_x2.dattr.attr, 146562306a36Sopenharmony_ci &ims_pcu_ofn_attr_scale_y2.dattr.attr, 146662306a36Sopenharmony_ci NULL 146762306a36Sopenharmony_ci}; 146862306a36Sopenharmony_ci 146962306a36Sopenharmony_cistatic const struct attribute_group ims_pcu_ofn_attr_group = { 147062306a36Sopenharmony_ci .name = "ofn", 147162306a36Sopenharmony_ci .attrs = ims_pcu_ofn_attrs, 147262306a36Sopenharmony_ci}; 147362306a36Sopenharmony_ci 147462306a36Sopenharmony_cistatic void ims_pcu_irq(struct urb *urb) 147562306a36Sopenharmony_ci{ 147662306a36Sopenharmony_ci struct ims_pcu *pcu = urb->context; 147762306a36Sopenharmony_ci int retval, status; 147862306a36Sopenharmony_ci 147962306a36Sopenharmony_ci status = urb->status; 148062306a36Sopenharmony_ci 148162306a36Sopenharmony_ci switch (status) { 148262306a36Sopenharmony_ci case 0: 148362306a36Sopenharmony_ci /* success */ 148462306a36Sopenharmony_ci break; 148562306a36Sopenharmony_ci case -ECONNRESET: 148662306a36Sopenharmony_ci case -ENOENT: 148762306a36Sopenharmony_ci case -ESHUTDOWN: 148862306a36Sopenharmony_ci /* this urb is terminated, clean up */ 148962306a36Sopenharmony_ci dev_dbg(pcu->dev, "%s - urb shutting down with status: %d\n", 149062306a36Sopenharmony_ci __func__, status); 149162306a36Sopenharmony_ci return; 149262306a36Sopenharmony_ci default: 149362306a36Sopenharmony_ci dev_dbg(pcu->dev, "%s - nonzero urb status received: %d\n", 149462306a36Sopenharmony_ci __func__, status); 149562306a36Sopenharmony_ci goto exit; 149662306a36Sopenharmony_ci } 149762306a36Sopenharmony_ci 149862306a36Sopenharmony_ci dev_dbg(pcu->dev, "%s: received %d: %*ph\n", __func__, 149962306a36Sopenharmony_ci urb->actual_length, urb->actual_length, pcu->urb_in_buf); 150062306a36Sopenharmony_ci 150162306a36Sopenharmony_ci if (urb == pcu->urb_in) 150262306a36Sopenharmony_ci ims_pcu_process_data(pcu, urb); 150362306a36Sopenharmony_ci 150462306a36Sopenharmony_ciexit: 150562306a36Sopenharmony_ci retval = usb_submit_urb(urb, GFP_ATOMIC); 150662306a36Sopenharmony_ci if (retval && retval != -ENODEV) 150762306a36Sopenharmony_ci dev_err(pcu->dev, "%s - usb_submit_urb failed with result %d\n", 150862306a36Sopenharmony_ci __func__, retval); 150962306a36Sopenharmony_ci} 151062306a36Sopenharmony_ci 151162306a36Sopenharmony_cistatic int ims_pcu_buffers_alloc(struct ims_pcu *pcu) 151262306a36Sopenharmony_ci{ 151362306a36Sopenharmony_ci int error; 151462306a36Sopenharmony_ci 151562306a36Sopenharmony_ci pcu->urb_in_buf = usb_alloc_coherent(pcu->udev, pcu->max_in_size, 151662306a36Sopenharmony_ci GFP_KERNEL, &pcu->read_dma); 151762306a36Sopenharmony_ci if (!pcu->urb_in_buf) { 151862306a36Sopenharmony_ci dev_err(pcu->dev, 151962306a36Sopenharmony_ci "Failed to allocate memory for read buffer\n"); 152062306a36Sopenharmony_ci return -ENOMEM; 152162306a36Sopenharmony_ci } 152262306a36Sopenharmony_ci 152362306a36Sopenharmony_ci pcu->urb_in = usb_alloc_urb(0, GFP_KERNEL); 152462306a36Sopenharmony_ci if (!pcu->urb_in) { 152562306a36Sopenharmony_ci dev_err(pcu->dev, "Failed to allocate input URB\n"); 152662306a36Sopenharmony_ci error = -ENOMEM; 152762306a36Sopenharmony_ci goto err_free_urb_in_buf; 152862306a36Sopenharmony_ci } 152962306a36Sopenharmony_ci 153062306a36Sopenharmony_ci pcu->urb_in->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; 153162306a36Sopenharmony_ci pcu->urb_in->transfer_dma = pcu->read_dma; 153262306a36Sopenharmony_ci 153362306a36Sopenharmony_ci usb_fill_bulk_urb(pcu->urb_in, pcu->udev, 153462306a36Sopenharmony_ci usb_rcvbulkpipe(pcu->udev, 153562306a36Sopenharmony_ci pcu->ep_in->bEndpointAddress), 153662306a36Sopenharmony_ci pcu->urb_in_buf, pcu->max_in_size, 153762306a36Sopenharmony_ci ims_pcu_irq, pcu); 153862306a36Sopenharmony_ci 153962306a36Sopenharmony_ci /* 154062306a36Sopenharmony_ci * We are using usb_bulk_msg() for sending so there is no point 154162306a36Sopenharmony_ci * in allocating memory with usb_alloc_coherent(). 154262306a36Sopenharmony_ci */ 154362306a36Sopenharmony_ci pcu->urb_out_buf = kmalloc(pcu->max_out_size, GFP_KERNEL); 154462306a36Sopenharmony_ci if (!pcu->urb_out_buf) { 154562306a36Sopenharmony_ci dev_err(pcu->dev, "Failed to allocate memory for write buffer\n"); 154662306a36Sopenharmony_ci error = -ENOMEM; 154762306a36Sopenharmony_ci goto err_free_in_urb; 154862306a36Sopenharmony_ci } 154962306a36Sopenharmony_ci 155062306a36Sopenharmony_ci pcu->urb_ctrl_buf = usb_alloc_coherent(pcu->udev, pcu->max_ctrl_size, 155162306a36Sopenharmony_ci GFP_KERNEL, &pcu->ctrl_dma); 155262306a36Sopenharmony_ci if (!pcu->urb_ctrl_buf) { 155362306a36Sopenharmony_ci dev_err(pcu->dev, 155462306a36Sopenharmony_ci "Failed to allocate memory for read buffer\n"); 155562306a36Sopenharmony_ci error = -ENOMEM; 155662306a36Sopenharmony_ci goto err_free_urb_out_buf; 155762306a36Sopenharmony_ci } 155862306a36Sopenharmony_ci 155962306a36Sopenharmony_ci pcu->urb_ctrl = usb_alloc_urb(0, GFP_KERNEL); 156062306a36Sopenharmony_ci if (!pcu->urb_ctrl) { 156162306a36Sopenharmony_ci dev_err(pcu->dev, "Failed to allocate input URB\n"); 156262306a36Sopenharmony_ci error = -ENOMEM; 156362306a36Sopenharmony_ci goto err_free_urb_ctrl_buf; 156462306a36Sopenharmony_ci } 156562306a36Sopenharmony_ci 156662306a36Sopenharmony_ci pcu->urb_ctrl->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; 156762306a36Sopenharmony_ci pcu->urb_ctrl->transfer_dma = pcu->ctrl_dma; 156862306a36Sopenharmony_ci 156962306a36Sopenharmony_ci usb_fill_int_urb(pcu->urb_ctrl, pcu->udev, 157062306a36Sopenharmony_ci usb_rcvintpipe(pcu->udev, 157162306a36Sopenharmony_ci pcu->ep_ctrl->bEndpointAddress), 157262306a36Sopenharmony_ci pcu->urb_ctrl_buf, pcu->max_ctrl_size, 157362306a36Sopenharmony_ci ims_pcu_irq, pcu, pcu->ep_ctrl->bInterval); 157462306a36Sopenharmony_ci 157562306a36Sopenharmony_ci return 0; 157662306a36Sopenharmony_ci 157762306a36Sopenharmony_cierr_free_urb_ctrl_buf: 157862306a36Sopenharmony_ci usb_free_coherent(pcu->udev, pcu->max_ctrl_size, 157962306a36Sopenharmony_ci pcu->urb_ctrl_buf, pcu->ctrl_dma); 158062306a36Sopenharmony_cierr_free_urb_out_buf: 158162306a36Sopenharmony_ci kfree(pcu->urb_out_buf); 158262306a36Sopenharmony_cierr_free_in_urb: 158362306a36Sopenharmony_ci usb_free_urb(pcu->urb_in); 158462306a36Sopenharmony_cierr_free_urb_in_buf: 158562306a36Sopenharmony_ci usb_free_coherent(pcu->udev, pcu->max_in_size, 158662306a36Sopenharmony_ci pcu->urb_in_buf, pcu->read_dma); 158762306a36Sopenharmony_ci return error; 158862306a36Sopenharmony_ci} 158962306a36Sopenharmony_ci 159062306a36Sopenharmony_cistatic void ims_pcu_buffers_free(struct ims_pcu *pcu) 159162306a36Sopenharmony_ci{ 159262306a36Sopenharmony_ci usb_kill_urb(pcu->urb_in); 159362306a36Sopenharmony_ci usb_free_urb(pcu->urb_in); 159462306a36Sopenharmony_ci 159562306a36Sopenharmony_ci usb_free_coherent(pcu->udev, pcu->max_out_size, 159662306a36Sopenharmony_ci pcu->urb_in_buf, pcu->read_dma); 159762306a36Sopenharmony_ci 159862306a36Sopenharmony_ci kfree(pcu->urb_out_buf); 159962306a36Sopenharmony_ci 160062306a36Sopenharmony_ci usb_kill_urb(pcu->urb_ctrl); 160162306a36Sopenharmony_ci usb_free_urb(pcu->urb_ctrl); 160262306a36Sopenharmony_ci 160362306a36Sopenharmony_ci usb_free_coherent(pcu->udev, pcu->max_ctrl_size, 160462306a36Sopenharmony_ci pcu->urb_ctrl_buf, pcu->ctrl_dma); 160562306a36Sopenharmony_ci} 160662306a36Sopenharmony_ci 160762306a36Sopenharmony_cistatic const struct usb_cdc_union_desc * 160862306a36Sopenharmony_ciims_pcu_get_cdc_union_desc(struct usb_interface *intf) 160962306a36Sopenharmony_ci{ 161062306a36Sopenharmony_ci const void *buf = intf->altsetting->extra; 161162306a36Sopenharmony_ci size_t buflen = intf->altsetting->extralen; 161262306a36Sopenharmony_ci struct usb_cdc_union_desc *union_desc; 161362306a36Sopenharmony_ci 161462306a36Sopenharmony_ci if (!buf) { 161562306a36Sopenharmony_ci dev_err(&intf->dev, "Missing descriptor data\n"); 161662306a36Sopenharmony_ci return NULL; 161762306a36Sopenharmony_ci } 161862306a36Sopenharmony_ci 161962306a36Sopenharmony_ci if (!buflen) { 162062306a36Sopenharmony_ci dev_err(&intf->dev, "Zero length descriptor\n"); 162162306a36Sopenharmony_ci return NULL; 162262306a36Sopenharmony_ci } 162362306a36Sopenharmony_ci 162462306a36Sopenharmony_ci while (buflen >= sizeof(*union_desc)) { 162562306a36Sopenharmony_ci union_desc = (struct usb_cdc_union_desc *)buf; 162662306a36Sopenharmony_ci 162762306a36Sopenharmony_ci if (union_desc->bLength > buflen) { 162862306a36Sopenharmony_ci dev_err(&intf->dev, "Too large descriptor\n"); 162962306a36Sopenharmony_ci return NULL; 163062306a36Sopenharmony_ci } 163162306a36Sopenharmony_ci 163262306a36Sopenharmony_ci if (union_desc->bDescriptorType == USB_DT_CS_INTERFACE && 163362306a36Sopenharmony_ci union_desc->bDescriptorSubType == USB_CDC_UNION_TYPE) { 163462306a36Sopenharmony_ci dev_dbg(&intf->dev, "Found union header\n"); 163562306a36Sopenharmony_ci 163662306a36Sopenharmony_ci if (union_desc->bLength >= sizeof(*union_desc)) 163762306a36Sopenharmony_ci return union_desc; 163862306a36Sopenharmony_ci 163962306a36Sopenharmony_ci dev_err(&intf->dev, 164062306a36Sopenharmony_ci "Union descriptor too short (%d vs %zd)\n", 164162306a36Sopenharmony_ci union_desc->bLength, sizeof(*union_desc)); 164262306a36Sopenharmony_ci return NULL; 164362306a36Sopenharmony_ci } 164462306a36Sopenharmony_ci 164562306a36Sopenharmony_ci buflen -= union_desc->bLength; 164662306a36Sopenharmony_ci buf += union_desc->bLength; 164762306a36Sopenharmony_ci } 164862306a36Sopenharmony_ci 164962306a36Sopenharmony_ci dev_err(&intf->dev, "Missing CDC union descriptor\n"); 165062306a36Sopenharmony_ci return NULL; 165162306a36Sopenharmony_ci} 165262306a36Sopenharmony_ci 165362306a36Sopenharmony_cistatic int ims_pcu_parse_cdc_data(struct usb_interface *intf, struct ims_pcu *pcu) 165462306a36Sopenharmony_ci{ 165562306a36Sopenharmony_ci const struct usb_cdc_union_desc *union_desc; 165662306a36Sopenharmony_ci struct usb_host_interface *alt; 165762306a36Sopenharmony_ci 165862306a36Sopenharmony_ci union_desc = ims_pcu_get_cdc_union_desc(intf); 165962306a36Sopenharmony_ci if (!union_desc) 166062306a36Sopenharmony_ci return -EINVAL; 166162306a36Sopenharmony_ci 166262306a36Sopenharmony_ci pcu->ctrl_intf = usb_ifnum_to_if(pcu->udev, 166362306a36Sopenharmony_ci union_desc->bMasterInterface0); 166462306a36Sopenharmony_ci if (!pcu->ctrl_intf) 166562306a36Sopenharmony_ci return -EINVAL; 166662306a36Sopenharmony_ci 166762306a36Sopenharmony_ci alt = pcu->ctrl_intf->cur_altsetting; 166862306a36Sopenharmony_ci 166962306a36Sopenharmony_ci if (alt->desc.bNumEndpoints < 1) 167062306a36Sopenharmony_ci return -ENODEV; 167162306a36Sopenharmony_ci 167262306a36Sopenharmony_ci pcu->ep_ctrl = &alt->endpoint[0].desc; 167362306a36Sopenharmony_ci pcu->max_ctrl_size = usb_endpoint_maxp(pcu->ep_ctrl); 167462306a36Sopenharmony_ci 167562306a36Sopenharmony_ci pcu->data_intf = usb_ifnum_to_if(pcu->udev, 167662306a36Sopenharmony_ci union_desc->bSlaveInterface0); 167762306a36Sopenharmony_ci if (!pcu->data_intf) 167862306a36Sopenharmony_ci return -EINVAL; 167962306a36Sopenharmony_ci 168062306a36Sopenharmony_ci alt = pcu->data_intf->cur_altsetting; 168162306a36Sopenharmony_ci if (alt->desc.bNumEndpoints != 2) { 168262306a36Sopenharmony_ci dev_err(pcu->dev, 168362306a36Sopenharmony_ci "Incorrect number of endpoints on data interface (%d)\n", 168462306a36Sopenharmony_ci alt->desc.bNumEndpoints); 168562306a36Sopenharmony_ci return -EINVAL; 168662306a36Sopenharmony_ci } 168762306a36Sopenharmony_ci 168862306a36Sopenharmony_ci pcu->ep_out = &alt->endpoint[0].desc; 168962306a36Sopenharmony_ci if (!usb_endpoint_is_bulk_out(pcu->ep_out)) { 169062306a36Sopenharmony_ci dev_err(pcu->dev, 169162306a36Sopenharmony_ci "First endpoint on data interface is not BULK OUT\n"); 169262306a36Sopenharmony_ci return -EINVAL; 169362306a36Sopenharmony_ci } 169462306a36Sopenharmony_ci 169562306a36Sopenharmony_ci pcu->max_out_size = usb_endpoint_maxp(pcu->ep_out); 169662306a36Sopenharmony_ci if (pcu->max_out_size < 8) { 169762306a36Sopenharmony_ci dev_err(pcu->dev, 169862306a36Sopenharmony_ci "Max OUT packet size is too small (%zd)\n", 169962306a36Sopenharmony_ci pcu->max_out_size); 170062306a36Sopenharmony_ci return -EINVAL; 170162306a36Sopenharmony_ci } 170262306a36Sopenharmony_ci 170362306a36Sopenharmony_ci pcu->ep_in = &alt->endpoint[1].desc; 170462306a36Sopenharmony_ci if (!usb_endpoint_is_bulk_in(pcu->ep_in)) { 170562306a36Sopenharmony_ci dev_err(pcu->dev, 170662306a36Sopenharmony_ci "Second endpoint on data interface is not BULK IN\n"); 170762306a36Sopenharmony_ci return -EINVAL; 170862306a36Sopenharmony_ci } 170962306a36Sopenharmony_ci 171062306a36Sopenharmony_ci pcu->max_in_size = usb_endpoint_maxp(pcu->ep_in); 171162306a36Sopenharmony_ci if (pcu->max_in_size < 8) { 171262306a36Sopenharmony_ci dev_err(pcu->dev, 171362306a36Sopenharmony_ci "Max IN packet size is too small (%zd)\n", 171462306a36Sopenharmony_ci pcu->max_in_size); 171562306a36Sopenharmony_ci return -EINVAL; 171662306a36Sopenharmony_ci } 171762306a36Sopenharmony_ci 171862306a36Sopenharmony_ci return 0; 171962306a36Sopenharmony_ci} 172062306a36Sopenharmony_ci 172162306a36Sopenharmony_cistatic int ims_pcu_start_io(struct ims_pcu *pcu) 172262306a36Sopenharmony_ci{ 172362306a36Sopenharmony_ci int error; 172462306a36Sopenharmony_ci 172562306a36Sopenharmony_ci error = usb_submit_urb(pcu->urb_ctrl, GFP_KERNEL); 172662306a36Sopenharmony_ci if (error) { 172762306a36Sopenharmony_ci dev_err(pcu->dev, 172862306a36Sopenharmony_ci "Failed to start control IO - usb_submit_urb failed with result: %d\n", 172962306a36Sopenharmony_ci error); 173062306a36Sopenharmony_ci return -EIO; 173162306a36Sopenharmony_ci } 173262306a36Sopenharmony_ci 173362306a36Sopenharmony_ci error = usb_submit_urb(pcu->urb_in, GFP_KERNEL); 173462306a36Sopenharmony_ci if (error) { 173562306a36Sopenharmony_ci dev_err(pcu->dev, 173662306a36Sopenharmony_ci "Failed to start IO - usb_submit_urb failed with result: %d\n", 173762306a36Sopenharmony_ci error); 173862306a36Sopenharmony_ci usb_kill_urb(pcu->urb_ctrl); 173962306a36Sopenharmony_ci return -EIO; 174062306a36Sopenharmony_ci } 174162306a36Sopenharmony_ci 174262306a36Sopenharmony_ci return 0; 174362306a36Sopenharmony_ci} 174462306a36Sopenharmony_ci 174562306a36Sopenharmony_cistatic void ims_pcu_stop_io(struct ims_pcu *pcu) 174662306a36Sopenharmony_ci{ 174762306a36Sopenharmony_ci usb_kill_urb(pcu->urb_in); 174862306a36Sopenharmony_ci usb_kill_urb(pcu->urb_ctrl); 174962306a36Sopenharmony_ci} 175062306a36Sopenharmony_ci 175162306a36Sopenharmony_cistatic int ims_pcu_line_setup(struct ims_pcu *pcu) 175262306a36Sopenharmony_ci{ 175362306a36Sopenharmony_ci struct usb_host_interface *interface = pcu->ctrl_intf->cur_altsetting; 175462306a36Sopenharmony_ci struct usb_cdc_line_coding *line = (void *)pcu->cmd_buf; 175562306a36Sopenharmony_ci int error; 175662306a36Sopenharmony_ci 175762306a36Sopenharmony_ci memset(line, 0, sizeof(*line)); 175862306a36Sopenharmony_ci line->dwDTERate = cpu_to_le32(57600); 175962306a36Sopenharmony_ci line->bDataBits = 8; 176062306a36Sopenharmony_ci 176162306a36Sopenharmony_ci error = usb_control_msg(pcu->udev, usb_sndctrlpipe(pcu->udev, 0), 176262306a36Sopenharmony_ci USB_CDC_REQ_SET_LINE_CODING, 176362306a36Sopenharmony_ci USB_TYPE_CLASS | USB_RECIP_INTERFACE, 176462306a36Sopenharmony_ci 0, interface->desc.bInterfaceNumber, 176562306a36Sopenharmony_ci line, sizeof(struct usb_cdc_line_coding), 176662306a36Sopenharmony_ci 5000); 176762306a36Sopenharmony_ci if (error < 0) { 176862306a36Sopenharmony_ci dev_err(pcu->dev, "Failed to set line coding, error: %d\n", 176962306a36Sopenharmony_ci error); 177062306a36Sopenharmony_ci return error; 177162306a36Sopenharmony_ci } 177262306a36Sopenharmony_ci 177362306a36Sopenharmony_ci error = usb_control_msg(pcu->udev, usb_sndctrlpipe(pcu->udev, 0), 177462306a36Sopenharmony_ci USB_CDC_REQ_SET_CONTROL_LINE_STATE, 177562306a36Sopenharmony_ci USB_TYPE_CLASS | USB_RECIP_INTERFACE, 177662306a36Sopenharmony_ci 0x03, interface->desc.bInterfaceNumber, 177762306a36Sopenharmony_ci NULL, 0, 5000); 177862306a36Sopenharmony_ci if (error < 0) { 177962306a36Sopenharmony_ci dev_err(pcu->dev, "Failed to set line state, error: %d\n", 178062306a36Sopenharmony_ci error); 178162306a36Sopenharmony_ci return error; 178262306a36Sopenharmony_ci } 178362306a36Sopenharmony_ci 178462306a36Sopenharmony_ci return 0; 178562306a36Sopenharmony_ci} 178662306a36Sopenharmony_ci 178762306a36Sopenharmony_cistatic int ims_pcu_get_device_info(struct ims_pcu *pcu) 178862306a36Sopenharmony_ci{ 178962306a36Sopenharmony_ci int error; 179062306a36Sopenharmony_ci 179162306a36Sopenharmony_ci error = ims_pcu_get_info(pcu); 179262306a36Sopenharmony_ci if (error) 179362306a36Sopenharmony_ci return error; 179462306a36Sopenharmony_ci 179562306a36Sopenharmony_ci error = ims_pcu_execute_query(pcu, GET_FW_VERSION); 179662306a36Sopenharmony_ci if (error) { 179762306a36Sopenharmony_ci dev_err(pcu->dev, 179862306a36Sopenharmony_ci "GET_FW_VERSION command failed, error: %d\n", error); 179962306a36Sopenharmony_ci return error; 180062306a36Sopenharmony_ci } 180162306a36Sopenharmony_ci 180262306a36Sopenharmony_ci snprintf(pcu->fw_version, sizeof(pcu->fw_version), 180362306a36Sopenharmony_ci "%02d%02d%02d%02d.%c%c", 180462306a36Sopenharmony_ci pcu->cmd_buf[2], pcu->cmd_buf[3], pcu->cmd_buf[4], pcu->cmd_buf[5], 180562306a36Sopenharmony_ci pcu->cmd_buf[6], pcu->cmd_buf[7]); 180662306a36Sopenharmony_ci 180762306a36Sopenharmony_ci error = ims_pcu_execute_query(pcu, GET_BL_VERSION); 180862306a36Sopenharmony_ci if (error) { 180962306a36Sopenharmony_ci dev_err(pcu->dev, 181062306a36Sopenharmony_ci "GET_BL_VERSION command failed, error: %d\n", error); 181162306a36Sopenharmony_ci return error; 181262306a36Sopenharmony_ci } 181362306a36Sopenharmony_ci 181462306a36Sopenharmony_ci snprintf(pcu->bl_version, sizeof(pcu->bl_version), 181562306a36Sopenharmony_ci "%02d%02d%02d%02d.%c%c", 181662306a36Sopenharmony_ci pcu->cmd_buf[2], pcu->cmd_buf[3], pcu->cmd_buf[4], pcu->cmd_buf[5], 181762306a36Sopenharmony_ci pcu->cmd_buf[6], pcu->cmd_buf[7]); 181862306a36Sopenharmony_ci 181962306a36Sopenharmony_ci error = ims_pcu_execute_query(pcu, RESET_REASON); 182062306a36Sopenharmony_ci if (error) { 182162306a36Sopenharmony_ci dev_err(pcu->dev, 182262306a36Sopenharmony_ci "RESET_REASON command failed, error: %d\n", error); 182362306a36Sopenharmony_ci return error; 182462306a36Sopenharmony_ci } 182562306a36Sopenharmony_ci 182662306a36Sopenharmony_ci snprintf(pcu->reset_reason, sizeof(pcu->reset_reason), 182762306a36Sopenharmony_ci "%02x", pcu->cmd_buf[IMS_PCU_DATA_OFFSET]); 182862306a36Sopenharmony_ci 182962306a36Sopenharmony_ci dev_dbg(pcu->dev, 183062306a36Sopenharmony_ci "P/N: %s, MD: %s, S/N: %s, FW: %s, BL: %s, RR: %s\n", 183162306a36Sopenharmony_ci pcu->part_number, 183262306a36Sopenharmony_ci pcu->date_of_manufacturing, 183362306a36Sopenharmony_ci pcu->serial_number, 183462306a36Sopenharmony_ci pcu->fw_version, 183562306a36Sopenharmony_ci pcu->bl_version, 183662306a36Sopenharmony_ci pcu->reset_reason); 183762306a36Sopenharmony_ci 183862306a36Sopenharmony_ci return 0; 183962306a36Sopenharmony_ci} 184062306a36Sopenharmony_ci 184162306a36Sopenharmony_cistatic int ims_pcu_identify_type(struct ims_pcu *pcu, u8 *device_id) 184262306a36Sopenharmony_ci{ 184362306a36Sopenharmony_ci int error; 184462306a36Sopenharmony_ci 184562306a36Sopenharmony_ci error = ims_pcu_execute_query(pcu, GET_DEVICE_ID); 184662306a36Sopenharmony_ci if (error) { 184762306a36Sopenharmony_ci dev_err(pcu->dev, 184862306a36Sopenharmony_ci "GET_DEVICE_ID command failed, error: %d\n", error); 184962306a36Sopenharmony_ci return error; 185062306a36Sopenharmony_ci } 185162306a36Sopenharmony_ci 185262306a36Sopenharmony_ci *device_id = pcu->cmd_buf[IMS_PCU_DATA_OFFSET]; 185362306a36Sopenharmony_ci dev_dbg(pcu->dev, "Detected device ID: %d\n", *device_id); 185462306a36Sopenharmony_ci 185562306a36Sopenharmony_ci return 0; 185662306a36Sopenharmony_ci} 185762306a36Sopenharmony_ci 185862306a36Sopenharmony_cistatic int ims_pcu_init_application_mode(struct ims_pcu *pcu) 185962306a36Sopenharmony_ci{ 186062306a36Sopenharmony_ci static atomic_t device_no = ATOMIC_INIT(-1); 186162306a36Sopenharmony_ci 186262306a36Sopenharmony_ci const struct ims_pcu_device_info *info; 186362306a36Sopenharmony_ci int error; 186462306a36Sopenharmony_ci 186562306a36Sopenharmony_ci error = ims_pcu_get_device_info(pcu); 186662306a36Sopenharmony_ci if (error) { 186762306a36Sopenharmony_ci /* Device does not respond to basic queries, hopeless */ 186862306a36Sopenharmony_ci return error; 186962306a36Sopenharmony_ci } 187062306a36Sopenharmony_ci 187162306a36Sopenharmony_ci error = ims_pcu_identify_type(pcu, &pcu->device_id); 187262306a36Sopenharmony_ci if (error) { 187362306a36Sopenharmony_ci dev_err(pcu->dev, 187462306a36Sopenharmony_ci "Failed to identify device, error: %d\n", error); 187562306a36Sopenharmony_ci /* 187662306a36Sopenharmony_ci * Do not signal error, but do not create input nor 187762306a36Sopenharmony_ci * backlight devices either, let userspace figure this 187862306a36Sopenharmony_ci * out (flash a new firmware?). 187962306a36Sopenharmony_ci */ 188062306a36Sopenharmony_ci return 0; 188162306a36Sopenharmony_ci } 188262306a36Sopenharmony_ci 188362306a36Sopenharmony_ci if (pcu->device_id >= ARRAY_SIZE(ims_pcu_device_info) || 188462306a36Sopenharmony_ci !ims_pcu_device_info[pcu->device_id].keymap) { 188562306a36Sopenharmony_ci dev_err(pcu->dev, "Device ID %d is not valid\n", pcu->device_id); 188662306a36Sopenharmony_ci /* Same as above, punt to userspace */ 188762306a36Sopenharmony_ci return 0; 188862306a36Sopenharmony_ci } 188962306a36Sopenharmony_ci 189062306a36Sopenharmony_ci /* Device appears to be operable, complete initialization */ 189162306a36Sopenharmony_ci pcu->device_no = atomic_inc_return(&device_no); 189262306a36Sopenharmony_ci 189362306a36Sopenharmony_ci /* 189462306a36Sopenharmony_ci * PCU-B devices, both GEN_1 and GEN_2 do not have OFN sensor 189562306a36Sopenharmony_ci */ 189662306a36Sopenharmony_ci if (pcu->device_id != IMS_PCU_PCU_B_DEVICE_ID) { 189762306a36Sopenharmony_ci error = sysfs_create_group(&pcu->dev->kobj, 189862306a36Sopenharmony_ci &ims_pcu_ofn_attr_group); 189962306a36Sopenharmony_ci if (error) 190062306a36Sopenharmony_ci return error; 190162306a36Sopenharmony_ci } 190262306a36Sopenharmony_ci 190362306a36Sopenharmony_ci error = ims_pcu_setup_backlight(pcu); 190462306a36Sopenharmony_ci if (error) 190562306a36Sopenharmony_ci return error; 190662306a36Sopenharmony_ci 190762306a36Sopenharmony_ci info = &ims_pcu_device_info[pcu->device_id]; 190862306a36Sopenharmony_ci error = ims_pcu_setup_buttons(pcu, info->keymap, info->keymap_len); 190962306a36Sopenharmony_ci if (error) 191062306a36Sopenharmony_ci goto err_destroy_backlight; 191162306a36Sopenharmony_ci 191262306a36Sopenharmony_ci if (info->has_gamepad) { 191362306a36Sopenharmony_ci error = ims_pcu_setup_gamepad(pcu); 191462306a36Sopenharmony_ci if (error) 191562306a36Sopenharmony_ci goto err_destroy_buttons; 191662306a36Sopenharmony_ci } 191762306a36Sopenharmony_ci 191862306a36Sopenharmony_ci pcu->setup_complete = true; 191962306a36Sopenharmony_ci 192062306a36Sopenharmony_ci return 0; 192162306a36Sopenharmony_ci 192262306a36Sopenharmony_cierr_destroy_buttons: 192362306a36Sopenharmony_ci ims_pcu_destroy_buttons(pcu); 192462306a36Sopenharmony_cierr_destroy_backlight: 192562306a36Sopenharmony_ci ims_pcu_destroy_backlight(pcu); 192662306a36Sopenharmony_ci return error; 192762306a36Sopenharmony_ci} 192862306a36Sopenharmony_ci 192962306a36Sopenharmony_cistatic void ims_pcu_destroy_application_mode(struct ims_pcu *pcu) 193062306a36Sopenharmony_ci{ 193162306a36Sopenharmony_ci if (pcu->setup_complete) { 193262306a36Sopenharmony_ci pcu->setup_complete = false; 193362306a36Sopenharmony_ci mb(); /* make sure flag setting is not reordered */ 193462306a36Sopenharmony_ci 193562306a36Sopenharmony_ci if (pcu->gamepad) 193662306a36Sopenharmony_ci ims_pcu_destroy_gamepad(pcu); 193762306a36Sopenharmony_ci ims_pcu_destroy_buttons(pcu); 193862306a36Sopenharmony_ci ims_pcu_destroy_backlight(pcu); 193962306a36Sopenharmony_ci 194062306a36Sopenharmony_ci if (pcu->device_id != IMS_PCU_PCU_B_DEVICE_ID) 194162306a36Sopenharmony_ci sysfs_remove_group(&pcu->dev->kobj, 194262306a36Sopenharmony_ci &ims_pcu_ofn_attr_group); 194362306a36Sopenharmony_ci } 194462306a36Sopenharmony_ci} 194562306a36Sopenharmony_ci 194662306a36Sopenharmony_cistatic int ims_pcu_init_bootloader_mode(struct ims_pcu *pcu) 194762306a36Sopenharmony_ci{ 194862306a36Sopenharmony_ci int error; 194962306a36Sopenharmony_ci 195062306a36Sopenharmony_ci error = ims_pcu_execute_bl_command(pcu, QUERY_DEVICE, NULL, 0, 195162306a36Sopenharmony_ci IMS_PCU_CMD_RESPONSE_TIMEOUT); 195262306a36Sopenharmony_ci if (error) { 195362306a36Sopenharmony_ci dev_err(pcu->dev, "Bootloader does not respond, aborting\n"); 195462306a36Sopenharmony_ci return error; 195562306a36Sopenharmony_ci } 195662306a36Sopenharmony_ci 195762306a36Sopenharmony_ci pcu->fw_start_addr = 195862306a36Sopenharmony_ci get_unaligned_le32(&pcu->cmd_buf[IMS_PCU_DATA_OFFSET + 11]); 195962306a36Sopenharmony_ci pcu->fw_end_addr = 196062306a36Sopenharmony_ci get_unaligned_le32(&pcu->cmd_buf[IMS_PCU_DATA_OFFSET + 15]); 196162306a36Sopenharmony_ci 196262306a36Sopenharmony_ci dev_info(pcu->dev, 196362306a36Sopenharmony_ci "Device is in bootloader mode (addr 0x%08x-0x%08x), requesting firmware\n", 196462306a36Sopenharmony_ci pcu->fw_start_addr, pcu->fw_end_addr); 196562306a36Sopenharmony_ci 196662306a36Sopenharmony_ci error = request_firmware_nowait(THIS_MODULE, true, 196762306a36Sopenharmony_ci IMS_PCU_FIRMWARE_NAME, 196862306a36Sopenharmony_ci pcu->dev, GFP_KERNEL, pcu, 196962306a36Sopenharmony_ci ims_pcu_process_async_firmware); 197062306a36Sopenharmony_ci if (error) { 197162306a36Sopenharmony_ci /* This error is not fatal, let userspace have another chance */ 197262306a36Sopenharmony_ci complete(&pcu->async_firmware_done); 197362306a36Sopenharmony_ci } 197462306a36Sopenharmony_ci 197562306a36Sopenharmony_ci return 0; 197662306a36Sopenharmony_ci} 197762306a36Sopenharmony_ci 197862306a36Sopenharmony_cistatic void ims_pcu_destroy_bootloader_mode(struct ims_pcu *pcu) 197962306a36Sopenharmony_ci{ 198062306a36Sopenharmony_ci /* Make sure our initial firmware request has completed */ 198162306a36Sopenharmony_ci wait_for_completion(&pcu->async_firmware_done); 198262306a36Sopenharmony_ci} 198362306a36Sopenharmony_ci 198462306a36Sopenharmony_ci#define IMS_PCU_APPLICATION_MODE 0 198562306a36Sopenharmony_ci#define IMS_PCU_BOOTLOADER_MODE 1 198662306a36Sopenharmony_ci 198762306a36Sopenharmony_cistatic struct usb_driver ims_pcu_driver; 198862306a36Sopenharmony_ci 198962306a36Sopenharmony_cistatic int ims_pcu_probe(struct usb_interface *intf, 199062306a36Sopenharmony_ci const struct usb_device_id *id) 199162306a36Sopenharmony_ci{ 199262306a36Sopenharmony_ci struct usb_device *udev = interface_to_usbdev(intf); 199362306a36Sopenharmony_ci struct ims_pcu *pcu; 199462306a36Sopenharmony_ci int error; 199562306a36Sopenharmony_ci 199662306a36Sopenharmony_ci pcu = kzalloc(sizeof(struct ims_pcu), GFP_KERNEL); 199762306a36Sopenharmony_ci if (!pcu) 199862306a36Sopenharmony_ci return -ENOMEM; 199962306a36Sopenharmony_ci 200062306a36Sopenharmony_ci pcu->dev = &intf->dev; 200162306a36Sopenharmony_ci pcu->udev = udev; 200262306a36Sopenharmony_ci pcu->bootloader_mode = id->driver_info == IMS_PCU_BOOTLOADER_MODE; 200362306a36Sopenharmony_ci mutex_init(&pcu->cmd_mutex); 200462306a36Sopenharmony_ci init_completion(&pcu->cmd_done); 200562306a36Sopenharmony_ci init_completion(&pcu->async_firmware_done); 200662306a36Sopenharmony_ci 200762306a36Sopenharmony_ci error = ims_pcu_parse_cdc_data(intf, pcu); 200862306a36Sopenharmony_ci if (error) 200962306a36Sopenharmony_ci goto err_free_mem; 201062306a36Sopenharmony_ci 201162306a36Sopenharmony_ci error = usb_driver_claim_interface(&ims_pcu_driver, 201262306a36Sopenharmony_ci pcu->data_intf, pcu); 201362306a36Sopenharmony_ci if (error) { 201462306a36Sopenharmony_ci dev_err(&intf->dev, 201562306a36Sopenharmony_ci "Unable to claim corresponding data interface: %d\n", 201662306a36Sopenharmony_ci error); 201762306a36Sopenharmony_ci goto err_free_mem; 201862306a36Sopenharmony_ci } 201962306a36Sopenharmony_ci 202062306a36Sopenharmony_ci usb_set_intfdata(pcu->ctrl_intf, pcu); 202162306a36Sopenharmony_ci 202262306a36Sopenharmony_ci error = ims_pcu_buffers_alloc(pcu); 202362306a36Sopenharmony_ci if (error) 202462306a36Sopenharmony_ci goto err_unclaim_intf; 202562306a36Sopenharmony_ci 202662306a36Sopenharmony_ci error = ims_pcu_start_io(pcu); 202762306a36Sopenharmony_ci if (error) 202862306a36Sopenharmony_ci goto err_free_buffers; 202962306a36Sopenharmony_ci 203062306a36Sopenharmony_ci error = ims_pcu_line_setup(pcu); 203162306a36Sopenharmony_ci if (error) 203262306a36Sopenharmony_ci goto err_stop_io; 203362306a36Sopenharmony_ci 203462306a36Sopenharmony_ci error = sysfs_create_group(&intf->dev.kobj, &ims_pcu_attr_group); 203562306a36Sopenharmony_ci if (error) 203662306a36Sopenharmony_ci goto err_stop_io; 203762306a36Sopenharmony_ci 203862306a36Sopenharmony_ci error = pcu->bootloader_mode ? 203962306a36Sopenharmony_ci ims_pcu_init_bootloader_mode(pcu) : 204062306a36Sopenharmony_ci ims_pcu_init_application_mode(pcu); 204162306a36Sopenharmony_ci if (error) 204262306a36Sopenharmony_ci goto err_remove_sysfs; 204362306a36Sopenharmony_ci 204462306a36Sopenharmony_ci return 0; 204562306a36Sopenharmony_ci 204662306a36Sopenharmony_cierr_remove_sysfs: 204762306a36Sopenharmony_ci sysfs_remove_group(&intf->dev.kobj, &ims_pcu_attr_group); 204862306a36Sopenharmony_cierr_stop_io: 204962306a36Sopenharmony_ci ims_pcu_stop_io(pcu); 205062306a36Sopenharmony_cierr_free_buffers: 205162306a36Sopenharmony_ci ims_pcu_buffers_free(pcu); 205262306a36Sopenharmony_cierr_unclaim_intf: 205362306a36Sopenharmony_ci usb_driver_release_interface(&ims_pcu_driver, pcu->data_intf); 205462306a36Sopenharmony_cierr_free_mem: 205562306a36Sopenharmony_ci kfree(pcu); 205662306a36Sopenharmony_ci return error; 205762306a36Sopenharmony_ci} 205862306a36Sopenharmony_ci 205962306a36Sopenharmony_cistatic void ims_pcu_disconnect(struct usb_interface *intf) 206062306a36Sopenharmony_ci{ 206162306a36Sopenharmony_ci struct ims_pcu *pcu = usb_get_intfdata(intf); 206262306a36Sopenharmony_ci struct usb_host_interface *alt = intf->cur_altsetting; 206362306a36Sopenharmony_ci 206462306a36Sopenharmony_ci usb_set_intfdata(intf, NULL); 206562306a36Sopenharmony_ci 206662306a36Sopenharmony_ci /* 206762306a36Sopenharmony_ci * See if we are dealing with control or data interface. The cleanup 206862306a36Sopenharmony_ci * happens when we unbind primary (control) interface. 206962306a36Sopenharmony_ci */ 207062306a36Sopenharmony_ci if (alt->desc.bInterfaceClass != USB_CLASS_COMM) 207162306a36Sopenharmony_ci return; 207262306a36Sopenharmony_ci 207362306a36Sopenharmony_ci sysfs_remove_group(&intf->dev.kobj, &ims_pcu_attr_group); 207462306a36Sopenharmony_ci 207562306a36Sopenharmony_ci ims_pcu_stop_io(pcu); 207662306a36Sopenharmony_ci 207762306a36Sopenharmony_ci if (pcu->bootloader_mode) 207862306a36Sopenharmony_ci ims_pcu_destroy_bootloader_mode(pcu); 207962306a36Sopenharmony_ci else 208062306a36Sopenharmony_ci ims_pcu_destroy_application_mode(pcu); 208162306a36Sopenharmony_ci 208262306a36Sopenharmony_ci ims_pcu_buffers_free(pcu); 208362306a36Sopenharmony_ci kfree(pcu); 208462306a36Sopenharmony_ci} 208562306a36Sopenharmony_ci 208662306a36Sopenharmony_ci#ifdef CONFIG_PM 208762306a36Sopenharmony_cistatic int ims_pcu_suspend(struct usb_interface *intf, 208862306a36Sopenharmony_ci pm_message_t message) 208962306a36Sopenharmony_ci{ 209062306a36Sopenharmony_ci struct ims_pcu *pcu = usb_get_intfdata(intf); 209162306a36Sopenharmony_ci struct usb_host_interface *alt = intf->cur_altsetting; 209262306a36Sopenharmony_ci 209362306a36Sopenharmony_ci if (alt->desc.bInterfaceClass == USB_CLASS_COMM) 209462306a36Sopenharmony_ci ims_pcu_stop_io(pcu); 209562306a36Sopenharmony_ci 209662306a36Sopenharmony_ci return 0; 209762306a36Sopenharmony_ci} 209862306a36Sopenharmony_ci 209962306a36Sopenharmony_cistatic int ims_pcu_resume(struct usb_interface *intf) 210062306a36Sopenharmony_ci{ 210162306a36Sopenharmony_ci struct ims_pcu *pcu = usb_get_intfdata(intf); 210262306a36Sopenharmony_ci struct usb_host_interface *alt = intf->cur_altsetting; 210362306a36Sopenharmony_ci int retval = 0; 210462306a36Sopenharmony_ci 210562306a36Sopenharmony_ci if (alt->desc.bInterfaceClass == USB_CLASS_COMM) { 210662306a36Sopenharmony_ci retval = ims_pcu_start_io(pcu); 210762306a36Sopenharmony_ci if (retval == 0) 210862306a36Sopenharmony_ci retval = ims_pcu_line_setup(pcu); 210962306a36Sopenharmony_ci } 211062306a36Sopenharmony_ci 211162306a36Sopenharmony_ci return retval; 211262306a36Sopenharmony_ci} 211362306a36Sopenharmony_ci#endif 211462306a36Sopenharmony_ci 211562306a36Sopenharmony_cistatic const struct usb_device_id ims_pcu_id_table[] = { 211662306a36Sopenharmony_ci { 211762306a36Sopenharmony_ci USB_DEVICE_AND_INTERFACE_INFO(0x04d8, 0x0082, 211862306a36Sopenharmony_ci USB_CLASS_COMM, 211962306a36Sopenharmony_ci USB_CDC_SUBCLASS_ACM, 212062306a36Sopenharmony_ci USB_CDC_ACM_PROTO_AT_V25TER), 212162306a36Sopenharmony_ci .driver_info = IMS_PCU_APPLICATION_MODE, 212262306a36Sopenharmony_ci }, 212362306a36Sopenharmony_ci { 212462306a36Sopenharmony_ci USB_DEVICE_AND_INTERFACE_INFO(0x04d8, 0x0083, 212562306a36Sopenharmony_ci USB_CLASS_COMM, 212662306a36Sopenharmony_ci USB_CDC_SUBCLASS_ACM, 212762306a36Sopenharmony_ci USB_CDC_ACM_PROTO_AT_V25TER), 212862306a36Sopenharmony_ci .driver_info = IMS_PCU_BOOTLOADER_MODE, 212962306a36Sopenharmony_ci }, 213062306a36Sopenharmony_ci { } 213162306a36Sopenharmony_ci}; 213262306a36Sopenharmony_ci 213362306a36Sopenharmony_cistatic struct usb_driver ims_pcu_driver = { 213462306a36Sopenharmony_ci .name = "ims_pcu", 213562306a36Sopenharmony_ci .id_table = ims_pcu_id_table, 213662306a36Sopenharmony_ci .probe = ims_pcu_probe, 213762306a36Sopenharmony_ci .disconnect = ims_pcu_disconnect, 213862306a36Sopenharmony_ci#ifdef CONFIG_PM 213962306a36Sopenharmony_ci .suspend = ims_pcu_suspend, 214062306a36Sopenharmony_ci .resume = ims_pcu_resume, 214162306a36Sopenharmony_ci .reset_resume = ims_pcu_resume, 214262306a36Sopenharmony_ci#endif 214362306a36Sopenharmony_ci}; 214462306a36Sopenharmony_ci 214562306a36Sopenharmony_cimodule_usb_driver(ims_pcu_driver); 214662306a36Sopenharmony_ci 214762306a36Sopenharmony_ciMODULE_DESCRIPTION("IMS Passenger Control Unit driver"); 214862306a36Sopenharmony_ciMODULE_AUTHOR("Dmitry Torokhov <dmitry.torokhov@gmail.com>"); 214962306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 2150