1628329d5SDmitry Torokhov /* 2628329d5SDmitry Torokhov * Driver for IMS Passenger Control Unit Devices 3628329d5SDmitry Torokhov * 4628329d5SDmitry Torokhov * Copyright (C) 2013 The IMS Company 5628329d5SDmitry Torokhov * 6628329d5SDmitry Torokhov * This program is free software; you can redistribute it and/or modify 7628329d5SDmitry Torokhov * it under the terms of the GNU General Public License version 2 8628329d5SDmitry Torokhov * as published by the Free Software Foundation. 9628329d5SDmitry Torokhov */ 10628329d5SDmitry Torokhov 11628329d5SDmitry Torokhov #include <linux/completion.h> 12628329d5SDmitry Torokhov #include <linux/device.h> 13628329d5SDmitry Torokhov #include <linux/firmware.h> 14628329d5SDmitry Torokhov #include <linux/ihex.h> 15628329d5SDmitry Torokhov #include <linux/input.h> 16628329d5SDmitry Torokhov #include <linux/kernel.h> 17628329d5SDmitry Torokhov #include <linux/leds.h> 18628329d5SDmitry Torokhov #include <linux/module.h> 19628329d5SDmitry Torokhov #include <linux/slab.h> 20628329d5SDmitry Torokhov #include <linux/types.h> 21628329d5SDmitry Torokhov #include <linux/usb/input.h> 22628329d5SDmitry Torokhov #include <linux/usb/cdc.h> 23628329d5SDmitry Torokhov #include <asm/unaligned.h> 24628329d5SDmitry Torokhov 25628329d5SDmitry Torokhov #define IMS_PCU_KEYMAP_LEN 32 26628329d5SDmitry Torokhov 27628329d5SDmitry Torokhov struct ims_pcu_buttons { 28628329d5SDmitry Torokhov struct input_dev *input; 29628329d5SDmitry Torokhov char name[32]; 30628329d5SDmitry Torokhov char phys[32]; 31628329d5SDmitry Torokhov unsigned short keymap[IMS_PCU_KEYMAP_LEN]; 32628329d5SDmitry Torokhov }; 33628329d5SDmitry Torokhov 34628329d5SDmitry Torokhov struct ims_pcu_gamepad { 35628329d5SDmitry Torokhov struct input_dev *input; 36628329d5SDmitry Torokhov char name[32]; 37628329d5SDmitry Torokhov char phys[32]; 38628329d5SDmitry Torokhov }; 39628329d5SDmitry Torokhov 40628329d5SDmitry Torokhov struct ims_pcu_backlight { 41628329d5SDmitry Torokhov struct led_classdev cdev; 42628329d5SDmitry Torokhov struct work_struct work; 43628329d5SDmitry Torokhov enum led_brightness desired_brightness; 44628329d5SDmitry Torokhov char name[32]; 45628329d5SDmitry Torokhov }; 46628329d5SDmitry Torokhov 47628329d5SDmitry Torokhov #define IMS_PCU_PART_NUMBER_LEN 15 48628329d5SDmitry Torokhov #define IMS_PCU_SERIAL_NUMBER_LEN 8 49628329d5SDmitry Torokhov #define IMS_PCU_DOM_LEN 8 50628329d5SDmitry Torokhov #define IMS_PCU_FW_VERSION_LEN (9 + 1) 51628329d5SDmitry Torokhov #define IMS_PCU_BL_VERSION_LEN (9 + 1) 52628329d5SDmitry Torokhov #define IMS_PCU_BL_RESET_REASON_LEN (2 + 1) 53628329d5SDmitry Torokhov 54628329d5SDmitry Torokhov #define IMS_PCU_BUF_SIZE 128 55628329d5SDmitry Torokhov 56628329d5SDmitry Torokhov struct ims_pcu { 57628329d5SDmitry Torokhov struct usb_device *udev; 58628329d5SDmitry Torokhov struct device *dev; /* control interface's device, used for logging */ 59628329d5SDmitry Torokhov 60628329d5SDmitry Torokhov unsigned int device_no; 61628329d5SDmitry Torokhov 62628329d5SDmitry Torokhov bool bootloader_mode; 63628329d5SDmitry Torokhov 64628329d5SDmitry Torokhov char part_number[IMS_PCU_PART_NUMBER_LEN]; 65628329d5SDmitry Torokhov char serial_number[IMS_PCU_SERIAL_NUMBER_LEN]; 66628329d5SDmitry Torokhov char date_of_manufacturing[IMS_PCU_DOM_LEN]; 67628329d5SDmitry Torokhov char fw_version[IMS_PCU_FW_VERSION_LEN]; 68628329d5SDmitry Torokhov char bl_version[IMS_PCU_BL_VERSION_LEN]; 69628329d5SDmitry Torokhov char reset_reason[IMS_PCU_BL_RESET_REASON_LEN]; 70628329d5SDmitry Torokhov int update_firmware_status; 71628329d5SDmitry Torokhov 72628329d5SDmitry Torokhov struct usb_interface *ctrl_intf; 73628329d5SDmitry Torokhov 74628329d5SDmitry Torokhov struct usb_endpoint_descriptor *ep_ctrl; 75628329d5SDmitry Torokhov struct urb *urb_ctrl; 76628329d5SDmitry Torokhov u8 *urb_ctrl_buf; 77628329d5SDmitry Torokhov dma_addr_t ctrl_dma; 78628329d5SDmitry Torokhov size_t max_ctrl_size; 79628329d5SDmitry Torokhov 80628329d5SDmitry Torokhov struct usb_interface *data_intf; 81628329d5SDmitry Torokhov 82628329d5SDmitry Torokhov struct usb_endpoint_descriptor *ep_in; 83628329d5SDmitry Torokhov struct urb *urb_in; 84628329d5SDmitry Torokhov u8 *urb_in_buf; 85628329d5SDmitry Torokhov dma_addr_t read_dma; 86628329d5SDmitry Torokhov size_t max_in_size; 87628329d5SDmitry Torokhov 88628329d5SDmitry Torokhov struct usb_endpoint_descriptor *ep_out; 89628329d5SDmitry Torokhov u8 *urb_out_buf; 90628329d5SDmitry Torokhov size_t max_out_size; 91628329d5SDmitry Torokhov 92628329d5SDmitry Torokhov u8 read_buf[IMS_PCU_BUF_SIZE]; 93628329d5SDmitry Torokhov u8 read_pos; 94628329d5SDmitry Torokhov u8 check_sum; 95628329d5SDmitry Torokhov bool have_stx; 96628329d5SDmitry Torokhov bool have_dle; 97628329d5SDmitry Torokhov 98628329d5SDmitry Torokhov u8 cmd_buf[IMS_PCU_BUF_SIZE]; 99628329d5SDmitry Torokhov u8 ack_id; 100628329d5SDmitry Torokhov u8 expected_response; 101628329d5SDmitry Torokhov u8 cmd_buf_len; 102628329d5SDmitry Torokhov struct completion cmd_done; 103628329d5SDmitry Torokhov struct mutex cmd_mutex; 104628329d5SDmitry Torokhov 105628329d5SDmitry Torokhov u32 fw_start_addr; 106628329d5SDmitry Torokhov u32 fw_end_addr; 107628329d5SDmitry Torokhov struct completion async_firmware_done; 108628329d5SDmitry Torokhov 109628329d5SDmitry Torokhov struct ims_pcu_buttons buttons; 110628329d5SDmitry Torokhov struct ims_pcu_gamepad *gamepad; 111628329d5SDmitry Torokhov struct ims_pcu_backlight backlight; 112628329d5SDmitry Torokhov 113628329d5SDmitry Torokhov bool setup_complete; /* Input and LED devices have been created */ 114628329d5SDmitry Torokhov }; 115628329d5SDmitry Torokhov 116628329d5SDmitry Torokhov 117628329d5SDmitry Torokhov /********************************************************************* 118628329d5SDmitry Torokhov * Buttons Input device support * 119628329d5SDmitry Torokhov *********************************************************************/ 120628329d5SDmitry Torokhov 121628329d5SDmitry Torokhov static const unsigned short ims_pcu_keymap_1[] = { 122628329d5SDmitry Torokhov [1] = KEY_ATTENDANT_OFF, 123628329d5SDmitry Torokhov [2] = KEY_ATTENDANT_ON, 124628329d5SDmitry Torokhov [3] = KEY_LIGHTS_TOGGLE, 125628329d5SDmitry Torokhov [4] = KEY_VOLUMEUP, 126628329d5SDmitry Torokhov [5] = KEY_VOLUMEDOWN, 127628329d5SDmitry Torokhov [6] = KEY_INFO, 128628329d5SDmitry Torokhov }; 129628329d5SDmitry Torokhov 130628329d5SDmitry Torokhov static const unsigned short ims_pcu_keymap_2[] = { 131628329d5SDmitry Torokhov [4] = KEY_VOLUMEUP, 132628329d5SDmitry Torokhov [5] = KEY_VOLUMEDOWN, 133628329d5SDmitry Torokhov [6] = KEY_INFO, 134628329d5SDmitry Torokhov }; 135628329d5SDmitry Torokhov 136628329d5SDmitry Torokhov static const unsigned short ims_pcu_keymap_3[] = { 137628329d5SDmitry Torokhov [1] = KEY_HOMEPAGE, 138628329d5SDmitry Torokhov [2] = KEY_ATTENDANT_TOGGLE, 139628329d5SDmitry Torokhov [3] = KEY_LIGHTS_TOGGLE, 140628329d5SDmitry Torokhov [4] = KEY_VOLUMEUP, 141628329d5SDmitry Torokhov [5] = KEY_VOLUMEDOWN, 142628329d5SDmitry Torokhov [6] = KEY_DISPLAYTOGGLE, 143628329d5SDmitry Torokhov [18] = KEY_PLAYPAUSE, 144628329d5SDmitry Torokhov }; 145628329d5SDmitry Torokhov 146628329d5SDmitry Torokhov static const unsigned short ims_pcu_keymap_4[] = { 147628329d5SDmitry Torokhov [1] = KEY_ATTENDANT_OFF, 148628329d5SDmitry Torokhov [2] = KEY_ATTENDANT_ON, 149628329d5SDmitry Torokhov [3] = KEY_LIGHTS_TOGGLE, 150628329d5SDmitry Torokhov [4] = KEY_VOLUMEUP, 151628329d5SDmitry Torokhov [5] = KEY_VOLUMEDOWN, 152628329d5SDmitry Torokhov [6] = KEY_INFO, 153628329d5SDmitry Torokhov [18] = KEY_PLAYPAUSE, 154628329d5SDmitry Torokhov }; 155628329d5SDmitry Torokhov 156628329d5SDmitry Torokhov static const unsigned short ims_pcu_keymap_5[] = { 157628329d5SDmitry Torokhov [1] = KEY_ATTENDANT_OFF, 158628329d5SDmitry Torokhov [2] = KEY_ATTENDANT_ON, 159628329d5SDmitry Torokhov [3] = KEY_LIGHTS_TOGGLE, 160628329d5SDmitry Torokhov }; 161628329d5SDmitry Torokhov 162628329d5SDmitry Torokhov struct ims_pcu_device_info { 163628329d5SDmitry Torokhov const unsigned short *keymap; 164628329d5SDmitry Torokhov size_t keymap_len; 165628329d5SDmitry Torokhov bool has_gamepad; 166628329d5SDmitry Torokhov }; 167628329d5SDmitry Torokhov 168628329d5SDmitry Torokhov #define IMS_PCU_DEVINFO(_n, _gamepad) \ 169628329d5SDmitry Torokhov [_n] = { \ 170628329d5SDmitry Torokhov .keymap = ims_pcu_keymap_##_n, \ 171628329d5SDmitry Torokhov .keymap_len = ARRAY_SIZE(ims_pcu_keymap_##_n), \ 172628329d5SDmitry Torokhov .has_gamepad = _gamepad, \ 173628329d5SDmitry Torokhov } 174628329d5SDmitry Torokhov 175628329d5SDmitry Torokhov static const struct ims_pcu_device_info ims_pcu_device_info[] = { 176628329d5SDmitry Torokhov IMS_PCU_DEVINFO(1, true), 177628329d5SDmitry Torokhov IMS_PCU_DEVINFO(2, true), 178628329d5SDmitry Torokhov IMS_PCU_DEVINFO(3, true), 179628329d5SDmitry Torokhov IMS_PCU_DEVINFO(4, true), 180628329d5SDmitry Torokhov IMS_PCU_DEVINFO(5, false), 181628329d5SDmitry Torokhov }; 182628329d5SDmitry Torokhov 183628329d5SDmitry Torokhov static void ims_pcu_buttons_report(struct ims_pcu *pcu, u32 data) 184628329d5SDmitry Torokhov { 185628329d5SDmitry Torokhov struct ims_pcu_buttons *buttons = &pcu->buttons; 186628329d5SDmitry Torokhov struct input_dev *input = buttons->input; 187628329d5SDmitry Torokhov int i; 188628329d5SDmitry Torokhov 189628329d5SDmitry Torokhov for (i = 0; i < 32; i++) { 190628329d5SDmitry Torokhov unsigned short keycode = buttons->keymap[i]; 191628329d5SDmitry Torokhov 192628329d5SDmitry Torokhov if (keycode != KEY_RESERVED) 193628329d5SDmitry Torokhov input_report_key(input, keycode, data & (1UL << i)); 194628329d5SDmitry Torokhov } 195628329d5SDmitry Torokhov 196628329d5SDmitry Torokhov input_sync(input); 197628329d5SDmitry Torokhov } 198628329d5SDmitry Torokhov 199628329d5SDmitry Torokhov static int ims_pcu_setup_buttons(struct ims_pcu *pcu, 200628329d5SDmitry Torokhov const unsigned short *keymap, 201628329d5SDmitry Torokhov size_t keymap_len) 202628329d5SDmitry Torokhov { 203628329d5SDmitry Torokhov struct ims_pcu_buttons *buttons = &pcu->buttons; 204628329d5SDmitry Torokhov struct input_dev *input; 205628329d5SDmitry Torokhov int i; 206628329d5SDmitry Torokhov int error; 207628329d5SDmitry Torokhov 208628329d5SDmitry Torokhov input = input_allocate_device(); 209628329d5SDmitry Torokhov if (!input) { 210628329d5SDmitry Torokhov dev_err(pcu->dev, 211628329d5SDmitry Torokhov "Not enough memory for input input device\n"); 212628329d5SDmitry Torokhov return -ENOMEM; 213628329d5SDmitry Torokhov } 214628329d5SDmitry Torokhov 215628329d5SDmitry Torokhov snprintf(buttons->name, sizeof(buttons->name), 216628329d5SDmitry Torokhov "IMS PCU#%d Button Interface", pcu->device_no); 217628329d5SDmitry Torokhov 218628329d5SDmitry Torokhov usb_make_path(pcu->udev, buttons->phys, sizeof(buttons->phys)); 219628329d5SDmitry Torokhov strlcat(buttons->phys, "/input0", sizeof(buttons->phys)); 220628329d5SDmitry Torokhov 221628329d5SDmitry Torokhov memcpy(buttons->keymap, keymap, sizeof(*keymap) * keymap_len); 222628329d5SDmitry Torokhov 223628329d5SDmitry Torokhov input->name = buttons->name; 224628329d5SDmitry Torokhov input->phys = buttons->phys; 225628329d5SDmitry Torokhov usb_to_input_id(pcu->udev, &input->id); 226628329d5SDmitry Torokhov input->dev.parent = &pcu->ctrl_intf->dev; 227628329d5SDmitry Torokhov 228628329d5SDmitry Torokhov input->keycode = buttons->keymap; 229628329d5SDmitry Torokhov input->keycodemax = ARRAY_SIZE(buttons->keymap); 230628329d5SDmitry Torokhov input->keycodesize = sizeof(buttons->keymap[0]); 231628329d5SDmitry Torokhov 232628329d5SDmitry Torokhov __set_bit(EV_KEY, input->evbit); 233628329d5SDmitry Torokhov for (i = 0; i < IMS_PCU_KEYMAP_LEN; i++) 234628329d5SDmitry Torokhov __set_bit(buttons->keymap[i], input->keybit); 235628329d5SDmitry Torokhov __clear_bit(KEY_RESERVED, input->keybit); 236628329d5SDmitry Torokhov 237628329d5SDmitry Torokhov error = input_register_device(input); 238628329d5SDmitry Torokhov if (error) { 239628329d5SDmitry Torokhov dev_err(pcu->dev, 240628329d5SDmitry Torokhov "Failed to register buttons input device: %d\n", 241628329d5SDmitry Torokhov error); 242628329d5SDmitry Torokhov input_free_device(input); 243628329d5SDmitry Torokhov return error; 244628329d5SDmitry Torokhov } 245628329d5SDmitry Torokhov 246628329d5SDmitry Torokhov buttons->input = input; 247628329d5SDmitry Torokhov return 0; 248628329d5SDmitry Torokhov } 249628329d5SDmitry Torokhov 250628329d5SDmitry Torokhov static void ims_pcu_destroy_buttons(struct ims_pcu *pcu) 251628329d5SDmitry Torokhov { 252628329d5SDmitry Torokhov struct ims_pcu_buttons *buttons = &pcu->buttons; 253628329d5SDmitry Torokhov 254628329d5SDmitry Torokhov input_unregister_device(buttons->input); 255628329d5SDmitry Torokhov } 256628329d5SDmitry Torokhov 257628329d5SDmitry Torokhov 258628329d5SDmitry Torokhov /********************************************************************* 259628329d5SDmitry Torokhov * Gamepad Input device support * 260628329d5SDmitry Torokhov *********************************************************************/ 261628329d5SDmitry Torokhov 262628329d5SDmitry Torokhov static void ims_pcu_gamepad_report(struct ims_pcu *pcu, u32 data) 263628329d5SDmitry Torokhov { 264628329d5SDmitry Torokhov struct ims_pcu_gamepad *gamepad = pcu->gamepad; 265628329d5SDmitry Torokhov struct input_dev *input = gamepad->input; 266628329d5SDmitry Torokhov int x, y; 267628329d5SDmitry Torokhov 268628329d5SDmitry Torokhov x = !!(data & (1 << 14)) - !!(data & (1 << 13)); 269628329d5SDmitry Torokhov y = !!(data & (1 << 12)) - !!(data & (1 << 11)); 270628329d5SDmitry Torokhov 271628329d5SDmitry Torokhov input_report_abs(input, ABS_X, x); 272628329d5SDmitry Torokhov input_report_abs(input, ABS_Y, y); 273628329d5SDmitry Torokhov 274628329d5SDmitry Torokhov input_report_key(input, BTN_A, data & (1 << 7)); 275628329d5SDmitry Torokhov input_report_key(input, BTN_B, data & (1 << 8)); 276628329d5SDmitry Torokhov input_report_key(input, BTN_X, data & (1 << 9)); 277628329d5SDmitry Torokhov input_report_key(input, BTN_Y, data & (1 << 10)); 278628329d5SDmitry Torokhov input_report_key(input, BTN_START, data & (1 << 15)); 279628329d5SDmitry Torokhov input_report_key(input, BTN_SELECT, data & (1 << 16)); 280628329d5SDmitry Torokhov 281628329d5SDmitry Torokhov input_sync(input); 282628329d5SDmitry Torokhov } 283628329d5SDmitry Torokhov 284628329d5SDmitry Torokhov static int ims_pcu_setup_gamepad(struct ims_pcu *pcu) 285628329d5SDmitry Torokhov { 286628329d5SDmitry Torokhov struct ims_pcu_gamepad *gamepad; 287628329d5SDmitry Torokhov struct input_dev *input; 288628329d5SDmitry Torokhov int error; 289628329d5SDmitry Torokhov 290628329d5SDmitry Torokhov gamepad = kzalloc(sizeof(struct ims_pcu_gamepad), GFP_KERNEL); 291628329d5SDmitry Torokhov input = input_allocate_device(); 292628329d5SDmitry Torokhov if (!gamepad || !input) { 293628329d5SDmitry Torokhov dev_err(pcu->dev, 294628329d5SDmitry Torokhov "Not enough memory for gamepad device\n"); 2953c2b9010SDmitry Torokhov error = -ENOMEM; 2963c2b9010SDmitry Torokhov goto err_free_mem; 297628329d5SDmitry Torokhov } 298628329d5SDmitry Torokhov 299628329d5SDmitry Torokhov gamepad->input = input; 300628329d5SDmitry Torokhov 301628329d5SDmitry Torokhov snprintf(gamepad->name, sizeof(gamepad->name), 302628329d5SDmitry Torokhov "IMS PCU#%d Gamepad Interface", pcu->device_no); 303628329d5SDmitry Torokhov 304628329d5SDmitry Torokhov usb_make_path(pcu->udev, gamepad->phys, sizeof(gamepad->phys)); 305628329d5SDmitry Torokhov strlcat(gamepad->phys, "/input1", sizeof(gamepad->phys)); 306628329d5SDmitry Torokhov 307628329d5SDmitry Torokhov input->name = gamepad->name; 308628329d5SDmitry Torokhov input->phys = gamepad->phys; 309628329d5SDmitry Torokhov usb_to_input_id(pcu->udev, &input->id); 310628329d5SDmitry Torokhov input->dev.parent = &pcu->ctrl_intf->dev; 311628329d5SDmitry Torokhov 312628329d5SDmitry Torokhov __set_bit(EV_KEY, input->evbit); 313628329d5SDmitry Torokhov __set_bit(BTN_A, input->keybit); 314628329d5SDmitry Torokhov __set_bit(BTN_B, input->keybit); 315628329d5SDmitry Torokhov __set_bit(BTN_X, input->keybit); 316628329d5SDmitry Torokhov __set_bit(BTN_Y, input->keybit); 317628329d5SDmitry Torokhov __set_bit(BTN_START, input->keybit); 318628329d5SDmitry Torokhov __set_bit(BTN_SELECT, input->keybit); 319628329d5SDmitry Torokhov 320628329d5SDmitry Torokhov __set_bit(EV_ABS, input->evbit); 321628329d5SDmitry Torokhov input_set_abs_params(input, ABS_X, -1, 1, 0, 0); 322628329d5SDmitry Torokhov input_set_abs_params(input, ABS_Y, -1, 1, 0, 0); 323628329d5SDmitry Torokhov 324628329d5SDmitry Torokhov error = input_register_device(input); 325628329d5SDmitry Torokhov if (error) { 326628329d5SDmitry Torokhov dev_err(pcu->dev, 327628329d5SDmitry Torokhov "Failed to register gamepad input device: %d\n", 328628329d5SDmitry Torokhov error); 329628329d5SDmitry Torokhov goto err_free_mem; 330628329d5SDmitry Torokhov } 331628329d5SDmitry Torokhov 332628329d5SDmitry Torokhov pcu->gamepad = gamepad; 333628329d5SDmitry Torokhov return 0; 334628329d5SDmitry Torokhov 335628329d5SDmitry Torokhov err_free_mem: 336628329d5SDmitry Torokhov input_free_device(input); 337628329d5SDmitry Torokhov kfree(gamepad); 338628329d5SDmitry Torokhov return -ENOMEM; 339628329d5SDmitry Torokhov } 340628329d5SDmitry Torokhov 341628329d5SDmitry Torokhov static void ims_pcu_destroy_gamepad(struct ims_pcu *pcu) 342628329d5SDmitry Torokhov { 343628329d5SDmitry Torokhov struct ims_pcu_gamepad *gamepad = pcu->gamepad; 344628329d5SDmitry Torokhov 345628329d5SDmitry Torokhov input_unregister_device(gamepad->input); 346628329d5SDmitry Torokhov kfree(gamepad); 347628329d5SDmitry Torokhov } 348628329d5SDmitry Torokhov 349628329d5SDmitry Torokhov 350628329d5SDmitry Torokhov /********************************************************************* 351628329d5SDmitry Torokhov * PCU Communication protocol handling * 352628329d5SDmitry Torokhov *********************************************************************/ 353628329d5SDmitry Torokhov 354628329d5SDmitry Torokhov #define IMS_PCU_PROTOCOL_STX 0x02 355628329d5SDmitry Torokhov #define IMS_PCU_PROTOCOL_ETX 0x03 356628329d5SDmitry Torokhov #define IMS_PCU_PROTOCOL_DLE 0x10 357628329d5SDmitry Torokhov 358628329d5SDmitry Torokhov /* PCU commands */ 359628329d5SDmitry Torokhov #define IMS_PCU_CMD_STATUS 0xa0 360628329d5SDmitry Torokhov #define IMS_PCU_CMD_PCU_RESET 0xa1 361628329d5SDmitry Torokhov #define IMS_PCU_CMD_RESET_REASON 0xa2 362628329d5SDmitry Torokhov #define IMS_PCU_CMD_SEND_BUTTONS 0xa3 363628329d5SDmitry Torokhov #define IMS_PCU_CMD_JUMP_TO_BTLDR 0xa4 364628329d5SDmitry Torokhov #define IMS_PCU_CMD_GET_INFO 0xa5 365628329d5SDmitry Torokhov #define IMS_PCU_CMD_SET_BRIGHTNESS 0xa6 366628329d5SDmitry Torokhov #define IMS_PCU_CMD_EEPROM 0xa7 367628329d5SDmitry Torokhov #define IMS_PCU_CMD_GET_FW_VERSION 0xa8 368628329d5SDmitry Torokhov #define IMS_PCU_CMD_GET_BL_VERSION 0xa9 369628329d5SDmitry Torokhov #define IMS_PCU_CMD_SET_INFO 0xab 370628329d5SDmitry Torokhov #define IMS_PCU_CMD_GET_BRIGHTNESS 0xac 371628329d5SDmitry Torokhov #define IMS_PCU_CMD_GET_DEVICE_ID 0xae 372628329d5SDmitry Torokhov #define IMS_PCU_CMD_SPECIAL_INFO 0xb0 373628329d5SDmitry Torokhov #define IMS_PCU_CMD_BOOTLOADER 0xb1 /* Pass data to bootloader */ 374628329d5SDmitry Torokhov 375628329d5SDmitry Torokhov /* PCU responses */ 376628329d5SDmitry Torokhov #define IMS_PCU_RSP_STATUS 0xc0 377628329d5SDmitry Torokhov #define IMS_PCU_RSP_PCU_RESET 0 /* Originally 0xc1 */ 378628329d5SDmitry Torokhov #define IMS_PCU_RSP_RESET_REASON 0xc2 379628329d5SDmitry Torokhov #define IMS_PCU_RSP_SEND_BUTTONS 0xc3 380628329d5SDmitry Torokhov #define IMS_PCU_RSP_JUMP_TO_BTLDR 0 /* Originally 0xc4 */ 381628329d5SDmitry Torokhov #define IMS_PCU_RSP_GET_INFO 0xc5 382628329d5SDmitry Torokhov #define IMS_PCU_RSP_SET_BRIGHTNESS 0xc6 383628329d5SDmitry Torokhov #define IMS_PCU_RSP_EEPROM 0xc7 384628329d5SDmitry Torokhov #define IMS_PCU_RSP_GET_FW_VERSION 0xc8 385628329d5SDmitry Torokhov #define IMS_PCU_RSP_GET_BL_VERSION 0xc9 386628329d5SDmitry Torokhov #define IMS_PCU_RSP_SET_INFO 0xcb 387628329d5SDmitry Torokhov #define IMS_PCU_RSP_GET_BRIGHTNESS 0xcc 388628329d5SDmitry Torokhov #define IMS_PCU_RSP_CMD_INVALID 0xcd 389628329d5SDmitry Torokhov #define IMS_PCU_RSP_GET_DEVICE_ID 0xce 390628329d5SDmitry Torokhov #define IMS_PCU_RSP_SPECIAL_INFO 0xd0 391628329d5SDmitry Torokhov #define IMS_PCU_RSP_BOOTLOADER 0xd1 /* Bootloader response */ 392628329d5SDmitry Torokhov 393628329d5SDmitry Torokhov #define IMS_PCU_RSP_EVNT_BUTTONS 0xe0 /* Unsolicited, button state */ 394628329d5SDmitry Torokhov #define IMS_PCU_GAMEPAD_MASK 0x0001ff80UL /* Bits 7 through 16 */ 395628329d5SDmitry Torokhov 396628329d5SDmitry Torokhov 397628329d5SDmitry Torokhov #define IMS_PCU_MIN_PACKET_LEN 3 398628329d5SDmitry Torokhov #define IMS_PCU_DATA_OFFSET 2 399628329d5SDmitry Torokhov 400628329d5SDmitry Torokhov #define IMS_PCU_CMD_WRITE_TIMEOUT 100 /* msec */ 401628329d5SDmitry Torokhov #define IMS_PCU_CMD_RESPONSE_TIMEOUT 500 /* msec */ 402628329d5SDmitry Torokhov 403628329d5SDmitry Torokhov static void ims_pcu_report_events(struct ims_pcu *pcu) 404628329d5SDmitry Torokhov { 405628329d5SDmitry Torokhov u32 data = get_unaligned_be32(&pcu->read_buf[3]); 406628329d5SDmitry Torokhov 407628329d5SDmitry Torokhov ims_pcu_buttons_report(pcu, data & ~IMS_PCU_GAMEPAD_MASK); 408628329d5SDmitry Torokhov if (pcu->gamepad) 409628329d5SDmitry Torokhov ims_pcu_gamepad_report(pcu, data); 410628329d5SDmitry Torokhov } 411628329d5SDmitry Torokhov 412628329d5SDmitry Torokhov static void ims_pcu_handle_response(struct ims_pcu *pcu) 413628329d5SDmitry Torokhov { 414628329d5SDmitry Torokhov switch (pcu->read_buf[0]) { 415628329d5SDmitry Torokhov case IMS_PCU_RSP_EVNT_BUTTONS: 416628329d5SDmitry Torokhov if (likely(pcu->setup_complete)) 417628329d5SDmitry Torokhov ims_pcu_report_events(pcu); 418628329d5SDmitry Torokhov break; 419628329d5SDmitry Torokhov 420628329d5SDmitry Torokhov default: 421628329d5SDmitry Torokhov /* 422628329d5SDmitry Torokhov * See if we got command completion. 423628329d5SDmitry Torokhov * If both the sequence and response code match save 424628329d5SDmitry Torokhov * the data and signal completion. 425628329d5SDmitry Torokhov */ 426628329d5SDmitry Torokhov if (pcu->read_buf[0] == pcu->expected_response && 427628329d5SDmitry Torokhov pcu->read_buf[1] == pcu->ack_id - 1) { 428628329d5SDmitry Torokhov 429628329d5SDmitry Torokhov memcpy(pcu->cmd_buf, pcu->read_buf, pcu->read_pos); 430628329d5SDmitry Torokhov pcu->cmd_buf_len = pcu->read_pos; 431628329d5SDmitry Torokhov complete(&pcu->cmd_done); 432628329d5SDmitry Torokhov } 433628329d5SDmitry Torokhov break; 434628329d5SDmitry Torokhov } 435628329d5SDmitry Torokhov } 436628329d5SDmitry Torokhov 437628329d5SDmitry Torokhov static void ims_pcu_process_data(struct ims_pcu *pcu, struct urb *urb) 438628329d5SDmitry Torokhov { 439628329d5SDmitry Torokhov int i; 440628329d5SDmitry Torokhov 441628329d5SDmitry Torokhov for (i = 0; i < urb->actual_length; i++) { 442628329d5SDmitry Torokhov u8 data = pcu->urb_in_buf[i]; 443628329d5SDmitry Torokhov 444628329d5SDmitry Torokhov /* Skip everything until we get Start Xmit */ 445628329d5SDmitry Torokhov if (!pcu->have_stx && data != IMS_PCU_PROTOCOL_STX) 446628329d5SDmitry Torokhov continue; 447628329d5SDmitry Torokhov 448628329d5SDmitry Torokhov if (pcu->have_dle) { 449628329d5SDmitry Torokhov pcu->have_dle = false; 450628329d5SDmitry Torokhov pcu->read_buf[pcu->read_pos++] = data; 451628329d5SDmitry Torokhov pcu->check_sum += data; 452628329d5SDmitry Torokhov continue; 453628329d5SDmitry Torokhov } 454628329d5SDmitry Torokhov 455628329d5SDmitry Torokhov switch (data) { 456628329d5SDmitry Torokhov case IMS_PCU_PROTOCOL_STX: 457628329d5SDmitry Torokhov if (pcu->have_stx) 458628329d5SDmitry Torokhov dev_warn(pcu->dev, 459628329d5SDmitry Torokhov "Unexpected STX at byte %d, discarding old data\n", 460628329d5SDmitry Torokhov pcu->read_pos); 461628329d5SDmitry Torokhov pcu->have_stx = true; 462628329d5SDmitry Torokhov pcu->have_dle = false; 463628329d5SDmitry Torokhov pcu->read_pos = 0; 464628329d5SDmitry Torokhov pcu->check_sum = 0; 465628329d5SDmitry Torokhov break; 466628329d5SDmitry Torokhov 467628329d5SDmitry Torokhov case IMS_PCU_PROTOCOL_DLE: 468628329d5SDmitry Torokhov pcu->have_dle = true; 469628329d5SDmitry Torokhov break; 470628329d5SDmitry Torokhov 471628329d5SDmitry Torokhov case IMS_PCU_PROTOCOL_ETX: 472628329d5SDmitry Torokhov if (pcu->read_pos < IMS_PCU_MIN_PACKET_LEN) { 473628329d5SDmitry Torokhov dev_warn(pcu->dev, 474628329d5SDmitry Torokhov "Short packet received (%d bytes), ignoring\n", 475628329d5SDmitry Torokhov pcu->read_pos); 476628329d5SDmitry Torokhov } else if (pcu->check_sum != 0) { 477628329d5SDmitry Torokhov dev_warn(pcu->dev, 478628329d5SDmitry Torokhov "Invalid checksum in packet (%d bytes), ignoring\n", 479628329d5SDmitry Torokhov pcu->read_pos); 480628329d5SDmitry Torokhov } else { 481628329d5SDmitry Torokhov ims_pcu_handle_response(pcu); 482628329d5SDmitry Torokhov } 483628329d5SDmitry Torokhov 484628329d5SDmitry Torokhov pcu->have_stx = false; 485628329d5SDmitry Torokhov pcu->have_dle = false; 486628329d5SDmitry Torokhov pcu->read_pos = 0; 487628329d5SDmitry Torokhov break; 488628329d5SDmitry Torokhov 489628329d5SDmitry Torokhov default: 490628329d5SDmitry Torokhov pcu->read_buf[pcu->read_pos++] = data; 491628329d5SDmitry Torokhov pcu->check_sum += data; 492628329d5SDmitry Torokhov break; 493628329d5SDmitry Torokhov } 494628329d5SDmitry Torokhov } 495628329d5SDmitry Torokhov } 496628329d5SDmitry Torokhov 497628329d5SDmitry Torokhov static bool ims_pcu_byte_needs_escape(u8 byte) 498628329d5SDmitry Torokhov { 499628329d5SDmitry Torokhov return byte == IMS_PCU_PROTOCOL_STX || 500628329d5SDmitry Torokhov byte == IMS_PCU_PROTOCOL_ETX || 501628329d5SDmitry Torokhov byte == IMS_PCU_PROTOCOL_DLE; 502628329d5SDmitry Torokhov } 503628329d5SDmitry Torokhov 504628329d5SDmitry Torokhov static int ims_pcu_send_cmd_chunk(struct ims_pcu *pcu, 505628329d5SDmitry Torokhov u8 command, int chunk, int len) 506628329d5SDmitry Torokhov { 507628329d5SDmitry Torokhov int error; 508628329d5SDmitry Torokhov 509628329d5SDmitry Torokhov error = usb_bulk_msg(pcu->udev, 510628329d5SDmitry Torokhov usb_sndbulkpipe(pcu->udev, 511628329d5SDmitry Torokhov pcu->ep_out->bEndpointAddress), 512628329d5SDmitry Torokhov pcu->urb_out_buf, len, 513628329d5SDmitry Torokhov NULL, IMS_PCU_CMD_WRITE_TIMEOUT); 514628329d5SDmitry Torokhov if (error < 0) { 515628329d5SDmitry Torokhov dev_dbg(pcu->dev, 516628329d5SDmitry Torokhov "Sending 0x%02x command failed at chunk %d: %d\n", 517628329d5SDmitry Torokhov command, chunk, error); 518628329d5SDmitry Torokhov return error; 519628329d5SDmitry Torokhov } 520628329d5SDmitry Torokhov 521628329d5SDmitry Torokhov return 0; 522628329d5SDmitry Torokhov } 523628329d5SDmitry Torokhov 524628329d5SDmitry Torokhov static int ims_pcu_send_command(struct ims_pcu *pcu, 525628329d5SDmitry Torokhov u8 command, const u8 *data, int len) 526628329d5SDmitry Torokhov { 527628329d5SDmitry Torokhov int count = 0; 528628329d5SDmitry Torokhov int chunk = 0; 529628329d5SDmitry Torokhov int delta; 530628329d5SDmitry Torokhov int i; 531628329d5SDmitry Torokhov int error; 532628329d5SDmitry Torokhov u8 csum = 0; 533628329d5SDmitry Torokhov u8 ack_id; 534628329d5SDmitry Torokhov 535628329d5SDmitry Torokhov pcu->urb_out_buf[count++] = IMS_PCU_PROTOCOL_STX; 536628329d5SDmitry Torokhov 537628329d5SDmitry Torokhov /* We know the command need not be escaped */ 538628329d5SDmitry Torokhov pcu->urb_out_buf[count++] = command; 539628329d5SDmitry Torokhov csum += command; 540628329d5SDmitry Torokhov 541628329d5SDmitry Torokhov ack_id = pcu->ack_id++; 542628329d5SDmitry Torokhov if (ack_id == 0xff) 543628329d5SDmitry Torokhov ack_id = pcu->ack_id++; 544628329d5SDmitry Torokhov 545628329d5SDmitry Torokhov if (ims_pcu_byte_needs_escape(ack_id)) 546628329d5SDmitry Torokhov pcu->urb_out_buf[count++] = IMS_PCU_PROTOCOL_DLE; 547628329d5SDmitry Torokhov 548628329d5SDmitry Torokhov pcu->urb_out_buf[count++] = ack_id; 549628329d5SDmitry Torokhov csum += ack_id; 550628329d5SDmitry Torokhov 551628329d5SDmitry Torokhov for (i = 0; i < len; i++) { 552628329d5SDmitry Torokhov 553628329d5SDmitry Torokhov delta = ims_pcu_byte_needs_escape(data[i]) ? 2 : 1; 554628329d5SDmitry Torokhov if (count + delta >= pcu->max_out_size) { 555628329d5SDmitry Torokhov error = ims_pcu_send_cmd_chunk(pcu, command, 556628329d5SDmitry Torokhov ++chunk, count); 557628329d5SDmitry Torokhov if (error) 558628329d5SDmitry Torokhov return error; 559628329d5SDmitry Torokhov 560628329d5SDmitry Torokhov count = 0; 561628329d5SDmitry Torokhov } 562628329d5SDmitry Torokhov 563628329d5SDmitry Torokhov if (delta == 2) 564628329d5SDmitry Torokhov pcu->urb_out_buf[count++] = IMS_PCU_PROTOCOL_DLE; 565628329d5SDmitry Torokhov 566628329d5SDmitry Torokhov pcu->urb_out_buf[count++] = data[i]; 567628329d5SDmitry Torokhov csum += data[i]; 568628329d5SDmitry Torokhov } 569628329d5SDmitry Torokhov 570628329d5SDmitry Torokhov csum = 1 + ~csum; 571628329d5SDmitry Torokhov 572628329d5SDmitry Torokhov delta = ims_pcu_byte_needs_escape(csum) ? 3 : 2; 573628329d5SDmitry Torokhov if (count + delta >= pcu->max_out_size) { 574628329d5SDmitry Torokhov error = ims_pcu_send_cmd_chunk(pcu, command, ++chunk, count); 575628329d5SDmitry Torokhov if (error) 576628329d5SDmitry Torokhov return error; 577628329d5SDmitry Torokhov 578628329d5SDmitry Torokhov count = 0; 579628329d5SDmitry Torokhov } 580628329d5SDmitry Torokhov 581628329d5SDmitry Torokhov if (delta == 3) 582628329d5SDmitry Torokhov pcu->urb_out_buf[count++] = IMS_PCU_PROTOCOL_DLE; 583628329d5SDmitry Torokhov 584628329d5SDmitry Torokhov pcu->urb_out_buf[count++] = csum; 585628329d5SDmitry Torokhov pcu->urb_out_buf[count++] = IMS_PCU_PROTOCOL_ETX; 586628329d5SDmitry Torokhov 587628329d5SDmitry Torokhov return ims_pcu_send_cmd_chunk(pcu, command, ++chunk, count); 588628329d5SDmitry Torokhov } 589628329d5SDmitry Torokhov 590628329d5SDmitry Torokhov static int __ims_pcu_execute_command(struct ims_pcu *pcu, 591628329d5SDmitry Torokhov u8 command, const void *data, size_t len, 592628329d5SDmitry Torokhov u8 expected_response, int response_time) 593628329d5SDmitry Torokhov { 594628329d5SDmitry Torokhov int error; 595628329d5SDmitry Torokhov 596628329d5SDmitry Torokhov pcu->expected_response = expected_response; 597628329d5SDmitry Torokhov init_completion(&pcu->cmd_done); 598628329d5SDmitry Torokhov 599628329d5SDmitry Torokhov error = ims_pcu_send_command(pcu, command, data, len); 600628329d5SDmitry Torokhov if (error) 601628329d5SDmitry Torokhov return error; 602628329d5SDmitry Torokhov 603628329d5SDmitry Torokhov if (expected_response && 604628329d5SDmitry Torokhov !wait_for_completion_timeout(&pcu->cmd_done, 605628329d5SDmitry Torokhov msecs_to_jiffies(response_time))) { 606628329d5SDmitry Torokhov dev_dbg(pcu->dev, "Command 0x%02x timed out\n", command); 607628329d5SDmitry Torokhov return -ETIMEDOUT; 608628329d5SDmitry Torokhov } 609628329d5SDmitry Torokhov 610628329d5SDmitry Torokhov return 0; 611628329d5SDmitry Torokhov } 612628329d5SDmitry Torokhov 613628329d5SDmitry Torokhov #define ims_pcu_execute_command(pcu, code, data, len) \ 614628329d5SDmitry Torokhov __ims_pcu_execute_command(pcu, \ 615628329d5SDmitry Torokhov IMS_PCU_CMD_##code, data, len, \ 616628329d5SDmitry Torokhov IMS_PCU_RSP_##code, \ 617628329d5SDmitry Torokhov IMS_PCU_CMD_RESPONSE_TIMEOUT) 618628329d5SDmitry Torokhov 619628329d5SDmitry Torokhov #define ims_pcu_execute_query(pcu, code) \ 620628329d5SDmitry Torokhov ims_pcu_execute_command(pcu, code, NULL, 0) 621628329d5SDmitry Torokhov 622628329d5SDmitry Torokhov /* Bootloader commands */ 623628329d5SDmitry Torokhov #define IMS_PCU_BL_CMD_QUERY_DEVICE 0xa1 624628329d5SDmitry Torokhov #define IMS_PCU_BL_CMD_UNLOCK_CONFIG 0xa2 625628329d5SDmitry Torokhov #define IMS_PCU_BL_CMD_ERASE_APP 0xa3 626628329d5SDmitry Torokhov #define IMS_PCU_BL_CMD_PROGRAM_DEVICE 0xa4 627628329d5SDmitry Torokhov #define IMS_PCU_BL_CMD_PROGRAM_COMPLETE 0xa5 628628329d5SDmitry Torokhov #define IMS_PCU_BL_CMD_READ_APP 0xa6 629628329d5SDmitry Torokhov #define IMS_PCU_BL_CMD_RESET_DEVICE 0xa7 630628329d5SDmitry Torokhov #define IMS_PCU_BL_CMD_LAUNCH_APP 0xa8 631628329d5SDmitry Torokhov 632628329d5SDmitry Torokhov /* Bootloader commands */ 633628329d5SDmitry Torokhov #define IMS_PCU_BL_RSP_QUERY_DEVICE 0xc1 634628329d5SDmitry Torokhov #define IMS_PCU_BL_RSP_UNLOCK_CONFIG 0xc2 635628329d5SDmitry Torokhov #define IMS_PCU_BL_RSP_ERASE_APP 0xc3 636628329d5SDmitry Torokhov #define IMS_PCU_BL_RSP_PROGRAM_DEVICE 0xc4 637628329d5SDmitry Torokhov #define IMS_PCU_BL_RSP_PROGRAM_COMPLETE 0xc5 638628329d5SDmitry Torokhov #define IMS_PCU_BL_RSP_READ_APP 0xc6 639628329d5SDmitry Torokhov #define IMS_PCU_BL_RSP_RESET_DEVICE 0 /* originally 0xa7 */ 640628329d5SDmitry Torokhov #define IMS_PCU_BL_RSP_LAUNCH_APP 0 /* originally 0xa8 */ 641628329d5SDmitry Torokhov 642628329d5SDmitry Torokhov #define IMS_PCU_BL_DATA_OFFSET 3 643628329d5SDmitry Torokhov 644628329d5SDmitry Torokhov static int __ims_pcu_execute_bl_command(struct ims_pcu *pcu, 645628329d5SDmitry Torokhov u8 command, const void *data, size_t len, 646628329d5SDmitry Torokhov u8 expected_response, int response_time) 647628329d5SDmitry Torokhov { 648628329d5SDmitry Torokhov int error; 649628329d5SDmitry Torokhov 650628329d5SDmitry Torokhov pcu->cmd_buf[0] = command; 651628329d5SDmitry Torokhov if (data) 652628329d5SDmitry Torokhov memcpy(&pcu->cmd_buf[1], data, len); 653628329d5SDmitry Torokhov 654628329d5SDmitry Torokhov error = __ims_pcu_execute_command(pcu, 655628329d5SDmitry Torokhov IMS_PCU_CMD_BOOTLOADER, pcu->cmd_buf, len + 1, 656628329d5SDmitry Torokhov expected_response ? IMS_PCU_RSP_BOOTLOADER : 0, 657628329d5SDmitry Torokhov response_time); 658628329d5SDmitry Torokhov if (error) { 659628329d5SDmitry Torokhov dev_err(pcu->dev, 660628329d5SDmitry Torokhov "Failure when sending 0x%02x command to bootloader, error: %d\n", 661628329d5SDmitry Torokhov pcu->cmd_buf[0], error); 662628329d5SDmitry Torokhov return error; 663628329d5SDmitry Torokhov } 664628329d5SDmitry Torokhov 665628329d5SDmitry Torokhov if (expected_response && pcu->cmd_buf[2] != expected_response) { 666628329d5SDmitry Torokhov dev_err(pcu->dev, 667628329d5SDmitry Torokhov "Unexpected response from bootloader: 0x%02x, wanted 0x%02x\n", 668628329d5SDmitry Torokhov pcu->cmd_buf[2], expected_response); 669628329d5SDmitry Torokhov return -EINVAL; 670628329d5SDmitry Torokhov } 671628329d5SDmitry Torokhov 672628329d5SDmitry Torokhov return 0; 673628329d5SDmitry Torokhov } 674628329d5SDmitry Torokhov 675628329d5SDmitry Torokhov #define ims_pcu_execute_bl_command(pcu, code, data, len, timeout) \ 676628329d5SDmitry Torokhov __ims_pcu_execute_bl_command(pcu, \ 677628329d5SDmitry Torokhov IMS_PCU_BL_CMD_##code, data, len, \ 678628329d5SDmitry Torokhov IMS_PCU_BL_RSP_##code, timeout) \ 679628329d5SDmitry Torokhov 680628329d5SDmitry Torokhov #define IMS_PCU_INFO_PART_OFFSET 2 681628329d5SDmitry Torokhov #define IMS_PCU_INFO_DOM_OFFSET 17 682628329d5SDmitry Torokhov #define IMS_PCU_INFO_SERIAL_OFFSET 25 683628329d5SDmitry Torokhov 684628329d5SDmitry Torokhov #define IMS_PCU_SET_INFO_SIZE 31 685628329d5SDmitry Torokhov 686628329d5SDmitry Torokhov static int ims_pcu_get_info(struct ims_pcu *pcu) 687628329d5SDmitry Torokhov { 688628329d5SDmitry Torokhov int error; 689628329d5SDmitry Torokhov 690628329d5SDmitry Torokhov error = ims_pcu_execute_query(pcu, GET_INFO); 691628329d5SDmitry Torokhov if (error) { 692628329d5SDmitry Torokhov dev_err(pcu->dev, 693628329d5SDmitry Torokhov "GET_INFO command failed, error: %d\n", error); 694628329d5SDmitry Torokhov return error; 695628329d5SDmitry Torokhov } 696628329d5SDmitry Torokhov 697628329d5SDmitry Torokhov memcpy(pcu->part_number, 698628329d5SDmitry Torokhov &pcu->cmd_buf[IMS_PCU_INFO_PART_OFFSET], 699628329d5SDmitry Torokhov sizeof(pcu->part_number)); 700628329d5SDmitry Torokhov memcpy(pcu->date_of_manufacturing, 701628329d5SDmitry Torokhov &pcu->cmd_buf[IMS_PCU_INFO_DOM_OFFSET], 702628329d5SDmitry Torokhov sizeof(pcu->date_of_manufacturing)); 703628329d5SDmitry Torokhov memcpy(pcu->serial_number, 704628329d5SDmitry Torokhov &pcu->cmd_buf[IMS_PCU_INFO_SERIAL_OFFSET], 705628329d5SDmitry Torokhov sizeof(pcu->serial_number)); 706628329d5SDmitry Torokhov 707628329d5SDmitry Torokhov return 0; 708628329d5SDmitry Torokhov } 709628329d5SDmitry Torokhov 710628329d5SDmitry Torokhov static int ims_pcu_set_info(struct ims_pcu *pcu) 711628329d5SDmitry Torokhov { 712628329d5SDmitry Torokhov int error; 713628329d5SDmitry Torokhov 714628329d5SDmitry Torokhov memcpy(&pcu->cmd_buf[IMS_PCU_INFO_PART_OFFSET], 715628329d5SDmitry Torokhov pcu->part_number, sizeof(pcu->part_number)); 716628329d5SDmitry Torokhov memcpy(&pcu->cmd_buf[IMS_PCU_INFO_DOM_OFFSET], 717628329d5SDmitry Torokhov pcu->date_of_manufacturing, sizeof(pcu->date_of_manufacturing)); 718628329d5SDmitry Torokhov memcpy(&pcu->cmd_buf[IMS_PCU_INFO_SERIAL_OFFSET], 719628329d5SDmitry Torokhov pcu->serial_number, sizeof(pcu->serial_number)); 720628329d5SDmitry Torokhov 721628329d5SDmitry Torokhov error = ims_pcu_execute_command(pcu, SET_INFO, 722628329d5SDmitry Torokhov &pcu->cmd_buf[IMS_PCU_DATA_OFFSET], 723628329d5SDmitry Torokhov IMS_PCU_SET_INFO_SIZE); 724628329d5SDmitry Torokhov if (error) { 725628329d5SDmitry Torokhov dev_err(pcu->dev, 726628329d5SDmitry Torokhov "Failed to update device information, error: %d\n", 727628329d5SDmitry Torokhov error); 728628329d5SDmitry Torokhov return error; 729628329d5SDmitry Torokhov } 730628329d5SDmitry Torokhov 731628329d5SDmitry Torokhov return 0; 732628329d5SDmitry Torokhov } 733628329d5SDmitry Torokhov 734628329d5SDmitry Torokhov static int ims_pcu_switch_to_bootloader(struct ims_pcu *pcu) 735628329d5SDmitry Torokhov { 736628329d5SDmitry Torokhov int error; 737628329d5SDmitry Torokhov 738628329d5SDmitry Torokhov /* Execute jump to the bootoloader */ 739628329d5SDmitry Torokhov error = ims_pcu_execute_command(pcu, JUMP_TO_BTLDR, NULL, 0); 740628329d5SDmitry Torokhov if (error) { 741628329d5SDmitry Torokhov dev_err(pcu->dev, 742628329d5SDmitry Torokhov "Failure when sending JUMP TO BOOLTLOADER command, error: %d\n", 743628329d5SDmitry Torokhov error); 744628329d5SDmitry Torokhov return error; 745628329d5SDmitry Torokhov } 746628329d5SDmitry Torokhov 747628329d5SDmitry Torokhov return 0; 748628329d5SDmitry Torokhov } 749628329d5SDmitry Torokhov 750628329d5SDmitry Torokhov /********************************************************************* 751628329d5SDmitry Torokhov * Firmware Update handling * 752628329d5SDmitry Torokhov *********************************************************************/ 753628329d5SDmitry Torokhov 754628329d5SDmitry Torokhov #define IMS_PCU_FIRMWARE_NAME "imspcu.fw" 755628329d5SDmitry Torokhov 756628329d5SDmitry Torokhov struct ims_pcu_flash_fmt { 757628329d5SDmitry Torokhov __le32 addr; 758628329d5SDmitry Torokhov u8 len; 759628329d5SDmitry Torokhov u8 data[]; 760628329d5SDmitry Torokhov }; 761628329d5SDmitry Torokhov 762628329d5SDmitry Torokhov static unsigned int ims_pcu_count_fw_records(const struct firmware *fw) 763628329d5SDmitry Torokhov { 764628329d5SDmitry Torokhov const struct ihex_binrec *rec = (const struct ihex_binrec *)fw->data; 765628329d5SDmitry Torokhov unsigned int count = 0; 766628329d5SDmitry Torokhov 767628329d5SDmitry Torokhov while (rec) { 768628329d5SDmitry Torokhov count++; 769628329d5SDmitry Torokhov rec = ihex_next_binrec(rec); 770628329d5SDmitry Torokhov } 771628329d5SDmitry Torokhov 772628329d5SDmitry Torokhov return count; 773628329d5SDmitry Torokhov } 774628329d5SDmitry Torokhov 775628329d5SDmitry Torokhov static int ims_pcu_verify_block(struct ims_pcu *pcu, 776628329d5SDmitry Torokhov u32 addr, u8 len, const u8 *data) 777628329d5SDmitry Torokhov { 778628329d5SDmitry Torokhov struct ims_pcu_flash_fmt *fragment; 779628329d5SDmitry Torokhov int error; 780628329d5SDmitry Torokhov 781628329d5SDmitry Torokhov fragment = (void *)&pcu->cmd_buf[1]; 782628329d5SDmitry Torokhov put_unaligned_le32(addr, &fragment->addr); 783628329d5SDmitry Torokhov fragment->len = len; 784628329d5SDmitry Torokhov 785628329d5SDmitry Torokhov error = ims_pcu_execute_bl_command(pcu, READ_APP, NULL, 5, 786628329d5SDmitry Torokhov IMS_PCU_CMD_RESPONSE_TIMEOUT); 787628329d5SDmitry Torokhov if (error) { 788628329d5SDmitry Torokhov dev_err(pcu->dev, 789628329d5SDmitry Torokhov "Failed to retrieve block at 0x%08x, len %d, error: %d\n", 790628329d5SDmitry Torokhov addr, len, error); 791628329d5SDmitry Torokhov return error; 792628329d5SDmitry Torokhov } 793628329d5SDmitry Torokhov 794628329d5SDmitry Torokhov fragment = (void *)&pcu->cmd_buf[IMS_PCU_BL_DATA_OFFSET]; 795628329d5SDmitry Torokhov if (get_unaligned_le32(&fragment->addr) != addr || 796628329d5SDmitry Torokhov fragment->len != len) { 797628329d5SDmitry Torokhov dev_err(pcu->dev, 798628329d5SDmitry Torokhov "Wrong block when retrieving 0x%08x (0x%08x), len %d (%d)\n", 799628329d5SDmitry Torokhov addr, get_unaligned_le32(&fragment->addr), 800628329d5SDmitry Torokhov len, fragment->len); 801628329d5SDmitry Torokhov return -EINVAL; 802628329d5SDmitry Torokhov } 803628329d5SDmitry Torokhov 804628329d5SDmitry Torokhov if (memcmp(fragment->data, data, len)) { 805628329d5SDmitry Torokhov dev_err(pcu->dev, 806628329d5SDmitry Torokhov "Mismatch in block at 0x%08x, len %d\n", 807628329d5SDmitry Torokhov addr, len); 808628329d5SDmitry Torokhov return -EINVAL; 809628329d5SDmitry Torokhov } 810628329d5SDmitry Torokhov 811628329d5SDmitry Torokhov return 0; 812628329d5SDmitry Torokhov } 813628329d5SDmitry Torokhov 814628329d5SDmitry Torokhov static int ims_pcu_flash_firmware(struct ims_pcu *pcu, 815628329d5SDmitry Torokhov const struct firmware *fw, 816628329d5SDmitry Torokhov unsigned int n_fw_records) 817628329d5SDmitry Torokhov { 818628329d5SDmitry Torokhov const struct ihex_binrec *rec = (const struct ihex_binrec *)fw->data; 819628329d5SDmitry Torokhov struct ims_pcu_flash_fmt *fragment; 820628329d5SDmitry Torokhov unsigned int count = 0; 821628329d5SDmitry Torokhov u32 addr; 822628329d5SDmitry Torokhov u8 len; 823628329d5SDmitry Torokhov int error; 824628329d5SDmitry Torokhov 825628329d5SDmitry Torokhov error = ims_pcu_execute_bl_command(pcu, ERASE_APP, NULL, 0, 2000); 826628329d5SDmitry Torokhov if (error) { 827628329d5SDmitry Torokhov dev_err(pcu->dev, 828628329d5SDmitry Torokhov "Failed to erase application image, error: %d\n", 829628329d5SDmitry Torokhov error); 830628329d5SDmitry Torokhov return error; 831628329d5SDmitry Torokhov } 832628329d5SDmitry Torokhov 833628329d5SDmitry Torokhov while (rec) { 834628329d5SDmitry Torokhov /* 835628329d5SDmitry Torokhov * The firmware format is messed up for some reason. 836628329d5SDmitry Torokhov * The address twice that of what is needed for some 837628329d5SDmitry Torokhov * reason and we end up overwriting half of the data 838628329d5SDmitry Torokhov * with the next record. 839628329d5SDmitry Torokhov */ 840628329d5SDmitry Torokhov addr = be32_to_cpu(rec->addr) / 2; 841628329d5SDmitry Torokhov len = be16_to_cpu(rec->len); 842628329d5SDmitry Torokhov 843628329d5SDmitry Torokhov fragment = (void *)&pcu->cmd_buf[1]; 844628329d5SDmitry Torokhov put_unaligned_le32(addr, &fragment->addr); 845628329d5SDmitry Torokhov fragment->len = len; 846628329d5SDmitry Torokhov memcpy(fragment->data, rec->data, len); 847628329d5SDmitry Torokhov 848628329d5SDmitry Torokhov error = ims_pcu_execute_bl_command(pcu, PROGRAM_DEVICE, 849628329d5SDmitry Torokhov NULL, len + 5, 850628329d5SDmitry Torokhov IMS_PCU_CMD_RESPONSE_TIMEOUT); 851628329d5SDmitry Torokhov if (error) { 852628329d5SDmitry Torokhov dev_err(pcu->dev, 853628329d5SDmitry Torokhov "Failed to write block at 0x%08x, len %d, error: %d\n", 854628329d5SDmitry Torokhov addr, len, error); 855628329d5SDmitry Torokhov return error; 856628329d5SDmitry Torokhov } 857628329d5SDmitry Torokhov 858628329d5SDmitry Torokhov if (addr >= pcu->fw_start_addr && addr < pcu->fw_end_addr) { 859628329d5SDmitry Torokhov error = ims_pcu_verify_block(pcu, addr, len, rec->data); 860628329d5SDmitry Torokhov if (error) 861628329d5SDmitry Torokhov return error; 862628329d5SDmitry Torokhov } 863628329d5SDmitry Torokhov 864628329d5SDmitry Torokhov count++; 865628329d5SDmitry Torokhov pcu->update_firmware_status = (count * 100) / n_fw_records; 866628329d5SDmitry Torokhov 867628329d5SDmitry Torokhov rec = ihex_next_binrec(rec); 868628329d5SDmitry Torokhov } 869628329d5SDmitry Torokhov 870628329d5SDmitry Torokhov error = ims_pcu_execute_bl_command(pcu, PROGRAM_COMPLETE, 871628329d5SDmitry Torokhov NULL, 0, 2000); 872628329d5SDmitry Torokhov if (error) 873628329d5SDmitry Torokhov dev_err(pcu->dev, 874628329d5SDmitry Torokhov "Failed to send PROGRAM_COMPLETE, error: %d\n", 875628329d5SDmitry Torokhov error); 876628329d5SDmitry Torokhov 877628329d5SDmitry Torokhov return 0; 878628329d5SDmitry Torokhov } 879628329d5SDmitry Torokhov 880628329d5SDmitry Torokhov static int ims_pcu_handle_firmware_update(struct ims_pcu *pcu, 881628329d5SDmitry Torokhov const struct firmware *fw) 882628329d5SDmitry Torokhov { 883628329d5SDmitry Torokhov unsigned int n_fw_records; 884628329d5SDmitry Torokhov int retval; 885628329d5SDmitry Torokhov 886628329d5SDmitry Torokhov dev_info(pcu->dev, "Updating firmware %s, size: %zu\n", 887628329d5SDmitry Torokhov IMS_PCU_FIRMWARE_NAME, fw->size); 888628329d5SDmitry Torokhov 889628329d5SDmitry Torokhov n_fw_records = ims_pcu_count_fw_records(fw); 890628329d5SDmitry Torokhov 891628329d5SDmitry Torokhov retval = ims_pcu_flash_firmware(pcu, fw, n_fw_records); 892628329d5SDmitry Torokhov if (retval) 893628329d5SDmitry Torokhov goto out; 894628329d5SDmitry Torokhov 895628329d5SDmitry Torokhov retval = ims_pcu_execute_bl_command(pcu, LAUNCH_APP, NULL, 0, 0); 896628329d5SDmitry Torokhov if (retval) 897628329d5SDmitry Torokhov dev_err(pcu->dev, 898628329d5SDmitry Torokhov "Failed to start application image, error: %d\n", 899628329d5SDmitry Torokhov retval); 900628329d5SDmitry Torokhov 901628329d5SDmitry Torokhov out: 902628329d5SDmitry Torokhov pcu->update_firmware_status = retval; 903628329d5SDmitry Torokhov sysfs_notify(&pcu->dev->kobj, NULL, "update_firmware_status"); 904628329d5SDmitry Torokhov return retval; 905628329d5SDmitry Torokhov } 906628329d5SDmitry Torokhov 907628329d5SDmitry Torokhov static void ims_pcu_process_async_firmware(const struct firmware *fw, 908628329d5SDmitry Torokhov void *context) 909628329d5SDmitry Torokhov { 910628329d5SDmitry Torokhov struct ims_pcu *pcu = context; 911628329d5SDmitry Torokhov int error; 912628329d5SDmitry Torokhov 913628329d5SDmitry Torokhov if (!fw) { 914628329d5SDmitry Torokhov dev_err(pcu->dev, "Failed to get firmware %s\n", 915628329d5SDmitry Torokhov IMS_PCU_FIRMWARE_NAME); 916628329d5SDmitry Torokhov goto out; 917628329d5SDmitry Torokhov } 918628329d5SDmitry Torokhov 919628329d5SDmitry Torokhov error = ihex_validate_fw(fw); 920628329d5SDmitry Torokhov if (error) { 921628329d5SDmitry Torokhov dev_err(pcu->dev, "Firmware %s is invalid\n", 922628329d5SDmitry Torokhov IMS_PCU_FIRMWARE_NAME); 923628329d5SDmitry Torokhov goto out; 924628329d5SDmitry Torokhov } 925628329d5SDmitry Torokhov 926628329d5SDmitry Torokhov mutex_lock(&pcu->cmd_mutex); 927628329d5SDmitry Torokhov ims_pcu_handle_firmware_update(pcu, fw); 928628329d5SDmitry Torokhov mutex_unlock(&pcu->cmd_mutex); 929628329d5SDmitry Torokhov 930628329d5SDmitry Torokhov release_firmware(fw); 931628329d5SDmitry Torokhov 932628329d5SDmitry Torokhov out: 933628329d5SDmitry Torokhov complete(&pcu->async_firmware_done); 934628329d5SDmitry Torokhov } 935628329d5SDmitry Torokhov 936628329d5SDmitry Torokhov /********************************************************************* 937628329d5SDmitry Torokhov * Backlight LED device support * 938628329d5SDmitry Torokhov *********************************************************************/ 939628329d5SDmitry Torokhov 940628329d5SDmitry Torokhov #define IMS_PCU_MAX_BRIGHTNESS 31998 941628329d5SDmitry Torokhov 942628329d5SDmitry Torokhov static void ims_pcu_backlight_work(struct work_struct *work) 943628329d5SDmitry Torokhov { 944628329d5SDmitry Torokhov struct ims_pcu_backlight *backlight = 945628329d5SDmitry Torokhov container_of(work, struct ims_pcu_backlight, work); 946628329d5SDmitry Torokhov struct ims_pcu *pcu = 947628329d5SDmitry Torokhov container_of(backlight, struct ims_pcu, backlight); 948628329d5SDmitry Torokhov int desired_brightness = backlight->desired_brightness; 949628329d5SDmitry Torokhov __le16 br_val = cpu_to_le16(desired_brightness); 950628329d5SDmitry Torokhov int error; 951628329d5SDmitry Torokhov 952628329d5SDmitry Torokhov mutex_lock(&pcu->cmd_mutex); 953628329d5SDmitry Torokhov 954628329d5SDmitry Torokhov error = ims_pcu_execute_command(pcu, SET_BRIGHTNESS, 955628329d5SDmitry Torokhov &br_val, sizeof(br_val)); 956628329d5SDmitry Torokhov if (error && error != -ENODEV) 957628329d5SDmitry Torokhov dev_warn(pcu->dev, 958628329d5SDmitry Torokhov "Failed to set desired brightness %u, error: %d\n", 959628329d5SDmitry Torokhov desired_brightness, error); 960628329d5SDmitry Torokhov 961628329d5SDmitry Torokhov mutex_unlock(&pcu->cmd_mutex); 962628329d5SDmitry Torokhov } 963628329d5SDmitry Torokhov 964628329d5SDmitry Torokhov static void ims_pcu_backlight_set_brightness(struct led_classdev *cdev, 965628329d5SDmitry Torokhov enum led_brightness value) 966628329d5SDmitry Torokhov { 967628329d5SDmitry Torokhov struct ims_pcu_backlight *backlight = 968628329d5SDmitry Torokhov container_of(cdev, struct ims_pcu_backlight, cdev); 969628329d5SDmitry Torokhov 970628329d5SDmitry Torokhov backlight->desired_brightness = value; 971628329d5SDmitry Torokhov schedule_work(&backlight->work); 972628329d5SDmitry Torokhov } 973628329d5SDmitry Torokhov 974628329d5SDmitry Torokhov static enum led_brightness 975628329d5SDmitry Torokhov ims_pcu_backlight_get_brightness(struct led_classdev *cdev) 976628329d5SDmitry Torokhov { 977628329d5SDmitry Torokhov struct ims_pcu_backlight *backlight = 978628329d5SDmitry Torokhov container_of(cdev, struct ims_pcu_backlight, cdev); 979628329d5SDmitry Torokhov struct ims_pcu *pcu = 980628329d5SDmitry Torokhov container_of(backlight, struct ims_pcu, backlight); 981628329d5SDmitry Torokhov int brightness; 982628329d5SDmitry Torokhov int error; 983628329d5SDmitry Torokhov 984628329d5SDmitry Torokhov mutex_lock(&pcu->cmd_mutex); 985628329d5SDmitry Torokhov 986628329d5SDmitry Torokhov error = ims_pcu_execute_query(pcu, GET_BRIGHTNESS); 987628329d5SDmitry Torokhov if (error) { 988628329d5SDmitry Torokhov dev_warn(pcu->dev, 989628329d5SDmitry Torokhov "Failed to get current brightness, error: %d\n", 990628329d5SDmitry Torokhov error); 991628329d5SDmitry Torokhov /* Assume the LED is OFF */ 992628329d5SDmitry Torokhov brightness = LED_OFF; 993628329d5SDmitry Torokhov } else { 994628329d5SDmitry Torokhov brightness = 995628329d5SDmitry Torokhov get_unaligned_le16(&pcu->cmd_buf[IMS_PCU_DATA_OFFSET]); 996628329d5SDmitry Torokhov } 997628329d5SDmitry Torokhov 998628329d5SDmitry Torokhov mutex_unlock(&pcu->cmd_mutex); 999628329d5SDmitry Torokhov 1000628329d5SDmitry Torokhov return brightness; 1001628329d5SDmitry Torokhov } 1002628329d5SDmitry Torokhov 1003628329d5SDmitry Torokhov static int ims_pcu_setup_backlight(struct ims_pcu *pcu) 1004628329d5SDmitry Torokhov { 1005628329d5SDmitry Torokhov struct ims_pcu_backlight *backlight = &pcu->backlight; 1006628329d5SDmitry Torokhov int error; 1007628329d5SDmitry Torokhov 1008628329d5SDmitry Torokhov INIT_WORK(&backlight->work, ims_pcu_backlight_work); 1009628329d5SDmitry Torokhov snprintf(backlight->name, sizeof(backlight->name), 1010628329d5SDmitry Torokhov "pcu%d::kbd_backlight", pcu->device_no); 1011628329d5SDmitry Torokhov 1012628329d5SDmitry Torokhov backlight->cdev.name = backlight->name; 1013628329d5SDmitry Torokhov backlight->cdev.max_brightness = IMS_PCU_MAX_BRIGHTNESS; 1014628329d5SDmitry Torokhov backlight->cdev.brightness_get = ims_pcu_backlight_get_brightness; 1015628329d5SDmitry Torokhov backlight->cdev.brightness_set = ims_pcu_backlight_set_brightness; 1016628329d5SDmitry Torokhov 1017628329d5SDmitry Torokhov error = led_classdev_register(pcu->dev, &backlight->cdev); 1018628329d5SDmitry Torokhov if (error) { 1019628329d5SDmitry Torokhov dev_err(pcu->dev, 1020628329d5SDmitry Torokhov "Failed to register backlight LED device, error: %d\n", 1021628329d5SDmitry Torokhov error); 1022628329d5SDmitry Torokhov return error; 1023628329d5SDmitry Torokhov } 1024628329d5SDmitry Torokhov 1025628329d5SDmitry Torokhov return 0; 1026628329d5SDmitry Torokhov } 1027628329d5SDmitry Torokhov 1028628329d5SDmitry Torokhov static void ims_pcu_destroy_backlight(struct ims_pcu *pcu) 1029628329d5SDmitry Torokhov { 1030628329d5SDmitry Torokhov struct ims_pcu_backlight *backlight = &pcu->backlight; 1031628329d5SDmitry Torokhov 1032628329d5SDmitry Torokhov led_classdev_unregister(&backlight->cdev); 1033628329d5SDmitry Torokhov cancel_work_sync(&backlight->work); 1034628329d5SDmitry Torokhov } 1035628329d5SDmitry Torokhov 1036628329d5SDmitry Torokhov 1037628329d5SDmitry Torokhov /********************************************************************* 1038628329d5SDmitry Torokhov * Sysfs attributes handling * 1039628329d5SDmitry Torokhov *********************************************************************/ 1040628329d5SDmitry Torokhov 1041628329d5SDmitry Torokhov struct ims_pcu_attribute { 1042628329d5SDmitry Torokhov struct device_attribute dattr; 1043628329d5SDmitry Torokhov size_t field_offset; 1044628329d5SDmitry Torokhov int field_length; 1045628329d5SDmitry Torokhov }; 1046628329d5SDmitry Torokhov 1047628329d5SDmitry Torokhov static ssize_t ims_pcu_attribute_show(struct device *dev, 1048628329d5SDmitry Torokhov struct device_attribute *dattr, 1049628329d5SDmitry Torokhov char *buf) 1050628329d5SDmitry Torokhov { 1051628329d5SDmitry Torokhov struct usb_interface *intf = to_usb_interface(dev); 1052628329d5SDmitry Torokhov struct ims_pcu *pcu = usb_get_intfdata(intf); 1053628329d5SDmitry Torokhov struct ims_pcu_attribute *attr = 1054628329d5SDmitry Torokhov container_of(dattr, struct ims_pcu_attribute, dattr); 1055628329d5SDmitry Torokhov char *field = (char *)pcu + attr->field_offset; 1056628329d5SDmitry Torokhov 1057628329d5SDmitry Torokhov return scnprintf(buf, PAGE_SIZE, "%.*s\n", attr->field_length, field); 1058628329d5SDmitry Torokhov } 1059628329d5SDmitry Torokhov 1060628329d5SDmitry Torokhov static ssize_t ims_pcu_attribute_store(struct device *dev, 1061628329d5SDmitry Torokhov struct device_attribute *dattr, 1062628329d5SDmitry Torokhov const char *buf, size_t count) 1063628329d5SDmitry Torokhov { 1064628329d5SDmitry Torokhov 1065628329d5SDmitry Torokhov struct usb_interface *intf = to_usb_interface(dev); 1066628329d5SDmitry Torokhov struct ims_pcu *pcu = usb_get_intfdata(intf); 1067628329d5SDmitry Torokhov struct ims_pcu_attribute *attr = 1068628329d5SDmitry Torokhov container_of(dattr, struct ims_pcu_attribute, dattr); 1069628329d5SDmitry Torokhov char *field = (char *)pcu + attr->field_offset; 1070628329d5SDmitry Torokhov size_t data_len; 1071628329d5SDmitry Torokhov int error; 1072628329d5SDmitry Torokhov 1073628329d5SDmitry Torokhov if (count > attr->field_length) 1074628329d5SDmitry Torokhov return -EINVAL; 1075628329d5SDmitry Torokhov 1076628329d5SDmitry Torokhov data_len = strnlen(buf, attr->field_length); 1077628329d5SDmitry Torokhov if (data_len > attr->field_length) 1078628329d5SDmitry Torokhov return -EINVAL; 1079628329d5SDmitry Torokhov 1080628329d5SDmitry Torokhov error = mutex_lock_interruptible(&pcu->cmd_mutex); 1081628329d5SDmitry Torokhov if (error) 1082628329d5SDmitry Torokhov return error; 1083628329d5SDmitry Torokhov 1084628329d5SDmitry Torokhov memset(field, 0, attr->field_length); 1085628329d5SDmitry Torokhov memcpy(field, buf, data_len); 1086628329d5SDmitry Torokhov 1087628329d5SDmitry Torokhov error = ims_pcu_set_info(pcu); 1088628329d5SDmitry Torokhov 1089628329d5SDmitry Torokhov /* 1090628329d5SDmitry Torokhov * Even if update failed, let's fetch the info again as we just 1091628329d5SDmitry Torokhov * clobbered one of the fields. 1092628329d5SDmitry Torokhov */ 1093628329d5SDmitry Torokhov ims_pcu_get_info(pcu); 1094628329d5SDmitry Torokhov 1095628329d5SDmitry Torokhov mutex_unlock(&pcu->cmd_mutex); 1096628329d5SDmitry Torokhov 1097628329d5SDmitry Torokhov return error < 0 ? error : count; 1098628329d5SDmitry Torokhov } 1099628329d5SDmitry Torokhov 1100628329d5SDmitry Torokhov #define IMS_PCU_ATTR(_field, _mode) \ 1101628329d5SDmitry Torokhov struct ims_pcu_attribute ims_pcu_attr_##_field = { \ 1102628329d5SDmitry Torokhov .dattr = __ATTR(_field, _mode, \ 1103628329d5SDmitry Torokhov ims_pcu_attribute_show, \ 1104628329d5SDmitry Torokhov ims_pcu_attribute_store), \ 1105628329d5SDmitry Torokhov .field_offset = offsetof(struct ims_pcu, _field), \ 1106628329d5SDmitry Torokhov .field_length = sizeof(((struct ims_pcu *)NULL)->_field), \ 1107628329d5SDmitry Torokhov } 1108628329d5SDmitry Torokhov 1109628329d5SDmitry Torokhov #define IMS_PCU_RO_ATTR(_field) \ 1110628329d5SDmitry Torokhov IMS_PCU_ATTR(_field, S_IRUGO) 1111628329d5SDmitry Torokhov #define IMS_PCU_RW_ATTR(_field) \ 1112628329d5SDmitry Torokhov IMS_PCU_ATTR(_field, S_IRUGO | S_IWUSR) 1113628329d5SDmitry Torokhov 1114628329d5SDmitry Torokhov static IMS_PCU_RW_ATTR(part_number); 1115628329d5SDmitry Torokhov static IMS_PCU_RW_ATTR(serial_number); 1116628329d5SDmitry Torokhov static IMS_PCU_RW_ATTR(date_of_manufacturing); 1117628329d5SDmitry Torokhov 1118628329d5SDmitry Torokhov static IMS_PCU_RO_ATTR(fw_version); 1119628329d5SDmitry Torokhov static IMS_PCU_RO_ATTR(bl_version); 1120628329d5SDmitry Torokhov static IMS_PCU_RO_ATTR(reset_reason); 1121628329d5SDmitry Torokhov 1122628329d5SDmitry Torokhov static ssize_t ims_pcu_reset_device(struct device *dev, 1123628329d5SDmitry Torokhov struct device_attribute *dattr, 1124628329d5SDmitry Torokhov const char *buf, size_t count) 1125628329d5SDmitry Torokhov { 1126628329d5SDmitry Torokhov static const u8 reset_byte = 1; 1127628329d5SDmitry Torokhov struct usb_interface *intf = to_usb_interface(dev); 1128628329d5SDmitry Torokhov struct ims_pcu *pcu = usb_get_intfdata(intf); 1129628329d5SDmitry Torokhov int value; 1130628329d5SDmitry Torokhov int error; 1131628329d5SDmitry Torokhov 1132628329d5SDmitry Torokhov error = kstrtoint(buf, 0, &value); 1133628329d5SDmitry Torokhov if (error) 1134628329d5SDmitry Torokhov return error; 1135628329d5SDmitry Torokhov 1136628329d5SDmitry Torokhov if (value != 1) 1137628329d5SDmitry Torokhov return -EINVAL; 1138628329d5SDmitry Torokhov 1139628329d5SDmitry Torokhov dev_info(pcu->dev, "Attempting to reset device\n"); 1140628329d5SDmitry Torokhov 1141628329d5SDmitry Torokhov error = ims_pcu_execute_command(pcu, PCU_RESET, &reset_byte, 1); 1142628329d5SDmitry Torokhov if (error) { 1143628329d5SDmitry Torokhov dev_info(pcu->dev, 1144628329d5SDmitry Torokhov "Failed to reset device, error: %d\n", 1145628329d5SDmitry Torokhov error); 1146628329d5SDmitry Torokhov return error; 1147628329d5SDmitry Torokhov } 1148628329d5SDmitry Torokhov 1149628329d5SDmitry Torokhov return count; 1150628329d5SDmitry Torokhov } 1151628329d5SDmitry Torokhov 1152628329d5SDmitry Torokhov static DEVICE_ATTR(reset_device, S_IWUSR, NULL, ims_pcu_reset_device); 1153628329d5SDmitry Torokhov 1154628329d5SDmitry Torokhov static ssize_t ims_pcu_update_firmware_store(struct device *dev, 1155628329d5SDmitry Torokhov struct device_attribute *dattr, 1156628329d5SDmitry Torokhov const char *buf, size_t count) 1157628329d5SDmitry Torokhov { 1158628329d5SDmitry Torokhov struct usb_interface *intf = to_usb_interface(dev); 1159628329d5SDmitry Torokhov struct ims_pcu *pcu = usb_get_intfdata(intf); 11603c2b9010SDmitry Torokhov const struct firmware *fw = NULL; 1161628329d5SDmitry Torokhov int value; 1162628329d5SDmitry Torokhov int error; 1163628329d5SDmitry Torokhov 1164628329d5SDmitry Torokhov error = kstrtoint(buf, 0, &value); 1165628329d5SDmitry Torokhov if (error) 1166628329d5SDmitry Torokhov return error; 1167628329d5SDmitry Torokhov 1168628329d5SDmitry Torokhov if (value != 1) 1169628329d5SDmitry Torokhov return -EINVAL; 1170628329d5SDmitry Torokhov 1171628329d5SDmitry Torokhov error = mutex_lock_interruptible(&pcu->cmd_mutex); 1172628329d5SDmitry Torokhov if (error) 1173628329d5SDmitry Torokhov return error; 1174628329d5SDmitry Torokhov 1175628329d5SDmitry Torokhov error = request_ihex_firmware(&fw, IMS_PCU_FIRMWARE_NAME, pcu->dev); 1176628329d5SDmitry Torokhov if (error) { 1177628329d5SDmitry Torokhov dev_err(pcu->dev, "Failed to request firmware %s, error: %d\n", 1178628329d5SDmitry Torokhov IMS_PCU_FIRMWARE_NAME, error); 1179628329d5SDmitry Torokhov goto out; 1180628329d5SDmitry Torokhov } 1181628329d5SDmitry Torokhov 1182628329d5SDmitry Torokhov /* 1183628329d5SDmitry Torokhov * If we are already in bootloader mode we can proceed with 1184628329d5SDmitry Torokhov * flashing the firmware. 1185628329d5SDmitry Torokhov * 1186628329d5SDmitry Torokhov * If we are in application mode, then we need to switch into 1187628329d5SDmitry Torokhov * bootloader mode, which will cause the device to disconnect 1188628329d5SDmitry Torokhov * and reconnect as different device. 1189628329d5SDmitry Torokhov */ 1190628329d5SDmitry Torokhov if (pcu->bootloader_mode) 1191628329d5SDmitry Torokhov error = ims_pcu_handle_firmware_update(pcu, fw); 1192628329d5SDmitry Torokhov else 1193628329d5SDmitry Torokhov error = ims_pcu_switch_to_bootloader(pcu); 1194628329d5SDmitry Torokhov 1195628329d5SDmitry Torokhov release_firmware(fw); 1196628329d5SDmitry Torokhov 1197628329d5SDmitry Torokhov out: 1198628329d5SDmitry Torokhov mutex_unlock(&pcu->cmd_mutex); 1199628329d5SDmitry Torokhov return error ?: count; 1200628329d5SDmitry Torokhov } 1201628329d5SDmitry Torokhov 1202628329d5SDmitry Torokhov static DEVICE_ATTR(update_firmware, S_IWUSR, 1203628329d5SDmitry Torokhov NULL, ims_pcu_update_firmware_store); 1204628329d5SDmitry Torokhov 1205628329d5SDmitry Torokhov static ssize_t 1206628329d5SDmitry Torokhov ims_pcu_update_firmware_status_show(struct device *dev, 1207628329d5SDmitry Torokhov struct device_attribute *dattr, 1208628329d5SDmitry Torokhov char *buf) 1209628329d5SDmitry Torokhov { 1210628329d5SDmitry Torokhov struct usb_interface *intf = to_usb_interface(dev); 1211628329d5SDmitry Torokhov struct ims_pcu *pcu = usb_get_intfdata(intf); 1212628329d5SDmitry Torokhov 1213628329d5SDmitry Torokhov return scnprintf(buf, PAGE_SIZE, "%d\n", pcu->update_firmware_status); 1214628329d5SDmitry Torokhov } 1215628329d5SDmitry Torokhov 1216628329d5SDmitry Torokhov static DEVICE_ATTR(update_firmware_status, S_IRUGO, 1217628329d5SDmitry Torokhov ims_pcu_update_firmware_status_show, NULL); 1218628329d5SDmitry Torokhov 1219628329d5SDmitry Torokhov static struct attribute *ims_pcu_attrs[] = { 1220628329d5SDmitry Torokhov &ims_pcu_attr_part_number.dattr.attr, 1221628329d5SDmitry Torokhov &ims_pcu_attr_serial_number.dattr.attr, 1222628329d5SDmitry Torokhov &ims_pcu_attr_date_of_manufacturing.dattr.attr, 1223628329d5SDmitry Torokhov &ims_pcu_attr_fw_version.dattr.attr, 1224628329d5SDmitry Torokhov &ims_pcu_attr_bl_version.dattr.attr, 1225628329d5SDmitry Torokhov &ims_pcu_attr_reset_reason.dattr.attr, 1226628329d5SDmitry Torokhov &dev_attr_reset_device.attr, 1227628329d5SDmitry Torokhov &dev_attr_update_firmware.attr, 1228628329d5SDmitry Torokhov &dev_attr_update_firmware_status.attr, 1229628329d5SDmitry Torokhov NULL 1230628329d5SDmitry Torokhov }; 1231628329d5SDmitry Torokhov 1232628329d5SDmitry Torokhov static umode_t ims_pcu_is_attr_visible(struct kobject *kobj, 1233628329d5SDmitry Torokhov struct attribute *attr, int n) 1234628329d5SDmitry Torokhov { 1235628329d5SDmitry Torokhov struct device *dev = container_of(kobj, struct device, kobj); 1236628329d5SDmitry Torokhov struct usb_interface *intf = to_usb_interface(dev); 1237628329d5SDmitry Torokhov struct ims_pcu *pcu = usb_get_intfdata(intf); 1238628329d5SDmitry Torokhov umode_t mode = attr->mode; 1239628329d5SDmitry Torokhov 1240628329d5SDmitry Torokhov if (pcu->bootloader_mode) { 1241628329d5SDmitry Torokhov if (attr != &dev_attr_update_firmware_status.attr && 1242628329d5SDmitry Torokhov attr != &dev_attr_update_firmware.attr && 1243628329d5SDmitry Torokhov attr != &dev_attr_reset_device.attr) { 1244628329d5SDmitry Torokhov mode = 0; 1245628329d5SDmitry Torokhov } 1246628329d5SDmitry Torokhov } else { 1247628329d5SDmitry Torokhov if (attr == &dev_attr_update_firmware_status.attr) 1248628329d5SDmitry Torokhov mode = 0; 1249628329d5SDmitry Torokhov } 1250628329d5SDmitry Torokhov 1251628329d5SDmitry Torokhov return mode; 1252628329d5SDmitry Torokhov } 1253628329d5SDmitry Torokhov 1254628329d5SDmitry Torokhov static struct attribute_group ims_pcu_attr_group = { 1255628329d5SDmitry Torokhov .is_visible = ims_pcu_is_attr_visible, 1256628329d5SDmitry Torokhov .attrs = ims_pcu_attrs, 1257628329d5SDmitry Torokhov }; 1258628329d5SDmitry Torokhov 1259628329d5SDmitry Torokhov static void ims_pcu_irq(struct urb *urb) 1260628329d5SDmitry Torokhov { 1261628329d5SDmitry Torokhov struct ims_pcu *pcu = urb->context; 1262628329d5SDmitry Torokhov int retval, status; 1263628329d5SDmitry Torokhov 1264628329d5SDmitry Torokhov status = urb->status; 1265628329d5SDmitry Torokhov 1266628329d5SDmitry Torokhov switch (status) { 1267628329d5SDmitry Torokhov case 0: 1268628329d5SDmitry Torokhov /* success */ 1269628329d5SDmitry Torokhov break; 1270628329d5SDmitry Torokhov case -ECONNRESET: 1271628329d5SDmitry Torokhov case -ENOENT: 1272628329d5SDmitry Torokhov case -ESHUTDOWN: 1273628329d5SDmitry Torokhov /* this urb is terminated, clean up */ 1274628329d5SDmitry Torokhov dev_dbg(pcu->dev, "%s - urb shutting down with status: %d\n", 1275628329d5SDmitry Torokhov __func__, status); 1276628329d5SDmitry Torokhov return; 1277628329d5SDmitry Torokhov default: 1278628329d5SDmitry Torokhov dev_dbg(pcu->dev, "%s - nonzero urb status received: %d\n", 1279628329d5SDmitry Torokhov __func__, status); 1280628329d5SDmitry Torokhov goto exit; 1281628329d5SDmitry Torokhov } 1282628329d5SDmitry Torokhov 1283628329d5SDmitry Torokhov dev_dbg(pcu->dev, "%s: received %d: %*ph\n", __func__, 1284628329d5SDmitry Torokhov urb->actual_length, urb->actual_length, pcu->urb_in_buf); 1285628329d5SDmitry Torokhov 1286628329d5SDmitry Torokhov if (urb == pcu->urb_in) 1287628329d5SDmitry Torokhov ims_pcu_process_data(pcu, urb); 1288628329d5SDmitry Torokhov 1289628329d5SDmitry Torokhov exit: 1290628329d5SDmitry Torokhov retval = usb_submit_urb(urb, GFP_ATOMIC); 1291628329d5SDmitry Torokhov if (retval && retval != -ENODEV) 1292628329d5SDmitry Torokhov dev_err(pcu->dev, "%s - usb_submit_urb failed with result %d\n", 1293628329d5SDmitry Torokhov __func__, retval); 1294628329d5SDmitry Torokhov } 1295628329d5SDmitry Torokhov 1296628329d5SDmitry Torokhov static int ims_pcu_buffers_alloc(struct ims_pcu *pcu) 1297628329d5SDmitry Torokhov { 1298628329d5SDmitry Torokhov int error; 1299628329d5SDmitry Torokhov 1300628329d5SDmitry Torokhov pcu->urb_in_buf = usb_alloc_coherent(pcu->udev, pcu->max_in_size, 1301628329d5SDmitry Torokhov GFP_KERNEL, &pcu->read_dma); 1302628329d5SDmitry Torokhov if (!pcu->urb_in_buf) { 1303628329d5SDmitry Torokhov dev_err(pcu->dev, 1304628329d5SDmitry Torokhov "Failed to allocate memory for read buffer\n"); 1305628329d5SDmitry Torokhov return -ENOMEM; 1306628329d5SDmitry Torokhov } 1307628329d5SDmitry Torokhov 1308628329d5SDmitry Torokhov pcu->urb_in = usb_alloc_urb(0, GFP_KERNEL); 1309628329d5SDmitry Torokhov if (!pcu->urb_in) { 1310628329d5SDmitry Torokhov dev_err(pcu->dev, "Failed to allocate input URB\n"); 1311628329d5SDmitry Torokhov error = -ENOMEM; 1312628329d5SDmitry Torokhov goto err_free_urb_in_buf; 1313628329d5SDmitry Torokhov } 1314628329d5SDmitry Torokhov 1315628329d5SDmitry Torokhov pcu->urb_in->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; 1316628329d5SDmitry Torokhov pcu->urb_in->transfer_dma = pcu->read_dma; 1317628329d5SDmitry Torokhov 1318628329d5SDmitry Torokhov usb_fill_bulk_urb(pcu->urb_in, pcu->udev, 1319628329d5SDmitry Torokhov usb_rcvbulkpipe(pcu->udev, 1320628329d5SDmitry Torokhov pcu->ep_in->bEndpointAddress), 1321628329d5SDmitry Torokhov pcu->urb_in_buf, pcu->max_in_size, 1322628329d5SDmitry Torokhov ims_pcu_irq, pcu); 1323628329d5SDmitry Torokhov 1324628329d5SDmitry Torokhov /* 1325628329d5SDmitry Torokhov * We are using usb_bulk_msg() for sending so there is no point 1326628329d5SDmitry Torokhov * in allocating memory with usb_alloc_coherent(). 1327628329d5SDmitry Torokhov */ 1328628329d5SDmitry Torokhov pcu->urb_out_buf = kmalloc(pcu->max_out_size, GFP_KERNEL); 1329628329d5SDmitry Torokhov if (!pcu->urb_out_buf) { 1330628329d5SDmitry Torokhov dev_err(pcu->dev, "Failed to allocate memory for write buffer\n"); 1331628329d5SDmitry Torokhov error = -ENOMEM; 1332628329d5SDmitry Torokhov goto err_free_in_urb; 1333628329d5SDmitry Torokhov } 1334628329d5SDmitry Torokhov 1335628329d5SDmitry Torokhov pcu->urb_ctrl_buf = usb_alloc_coherent(pcu->udev, pcu->max_ctrl_size, 1336628329d5SDmitry Torokhov GFP_KERNEL, &pcu->ctrl_dma); 1337628329d5SDmitry Torokhov if (!pcu->urb_ctrl_buf) { 1338628329d5SDmitry Torokhov dev_err(pcu->dev, 1339628329d5SDmitry Torokhov "Failed to allocate memory for read buffer\n"); 1340628329d5SDmitry Torokhov goto err_free_urb_out_buf; 1341628329d5SDmitry Torokhov } 1342628329d5SDmitry Torokhov 1343628329d5SDmitry Torokhov pcu->urb_ctrl = usb_alloc_urb(0, GFP_KERNEL); 1344628329d5SDmitry Torokhov if (!pcu->urb_ctrl) { 1345628329d5SDmitry Torokhov dev_err(pcu->dev, "Failed to allocate input URB\n"); 1346628329d5SDmitry Torokhov error = -ENOMEM; 1347628329d5SDmitry Torokhov goto err_free_urb_ctrl_buf; 1348628329d5SDmitry Torokhov } 1349628329d5SDmitry Torokhov 1350628329d5SDmitry Torokhov pcu->urb_ctrl->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; 1351628329d5SDmitry Torokhov pcu->urb_ctrl->transfer_dma = pcu->ctrl_dma; 1352628329d5SDmitry Torokhov 1353628329d5SDmitry Torokhov usb_fill_int_urb(pcu->urb_ctrl, pcu->udev, 1354628329d5SDmitry Torokhov usb_rcvintpipe(pcu->udev, 1355628329d5SDmitry Torokhov pcu->ep_ctrl->bEndpointAddress), 1356628329d5SDmitry Torokhov pcu->urb_ctrl_buf, pcu->max_ctrl_size, 1357628329d5SDmitry Torokhov ims_pcu_irq, pcu, pcu->ep_ctrl->bInterval); 1358628329d5SDmitry Torokhov 1359628329d5SDmitry Torokhov return 0; 1360628329d5SDmitry Torokhov 1361628329d5SDmitry Torokhov err_free_urb_ctrl_buf: 1362628329d5SDmitry Torokhov usb_free_coherent(pcu->udev, pcu->max_ctrl_size, 1363628329d5SDmitry Torokhov pcu->urb_ctrl_buf, pcu->ctrl_dma); 1364628329d5SDmitry Torokhov err_free_urb_out_buf: 1365628329d5SDmitry Torokhov kfree(pcu->urb_out_buf); 1366628329d5SDmitry Torokhov err_free_in_urb: 1367628329d5SDmitry Torokhov usb_free_urb(pcu->urb_in); 1368628329d5SDmitry Torokhov err_free_urb_in_buf: 1369628329d5SDmitry Torokhov usb_free_coherent(pcu->udev, pcu->max_in_size, 1370628329d5SDmitry Torokhov pcu->urb_in_buf, pcu->read_dma); 1371628329d5SDmitry Torokhov return error; 1372628329d5SDmitry Torokhov } 1373628329d5SDmitry Torokhov 1374628329d5SDmitry Torokhov static void ims_pcu_buffers_free(struct ims_pcu *pcu) 1375628329d5SDmitry Torokhov { 1376628329d5SDmitry Torokhov usb_kill_urb(pcu->urb_in); 1377628329d5SDmitry Torokhov usb_free_urb(pcu->urb_in); 1378628329d5SDmitry Torokhov 1379628329d5SDmitry Torokhov usb_free_coherent(pcu->udev, pcu->max_out_size, 1380628329d5SDmitry Torokhov pcu->urb_in_buf, pcu->read_dma); 1381628329d5SDmitry Torokhov 1382628329d5SDmitry Torokhov kfree(pcu->urb_out_buf); 1383628329d5SDmitry Torokhov 1384628329d5SDmitry Torokhov usb_kill_urb(pcu->urb_ctrl); 1385628329d5SDmitry Torokhov usb_free_urb(pcu->urb_ctrl); 1386628329d5SDmitry Torokhov 1387628329d5SDmitry Torokhov usb_free_coherent(pcu->udev, pcu->max_ctrl_size, 1388628329d5SDmitry Torokhov pcu->urb_ctrl_buf, pcu->ctrl_dma); 1389628329d5SDmitry Torokhov } 1390628329d5SDmitry Torokhov 1391628329d5SDmitry Torokhov static const struct usb_cdc_union_desc * 1392628329d5SDmitry Torokhov ims_pcu_get_cdc_union_desc(struct usb_interface *intf) 1393628329d5SDmitry Torokhov { 1394628329d5SDmitry Torokhov const void *buf = intf->altsetting->extra; 1395628329d5SDmitry Torokhov size_t buflen = intf->altsetting->extralen; 1396628329d5SDmitry Torokhov struct usb_cdc_union_desc *union_desc; 1397628329d5SDmitry Torokhov 1398628329d5SDmitry Torokhov if (!buf) { 1399628329d5SDmitry Torokhov dev_err(&intf->dev, "Missing descriptor data\n"); 1400628329d5SDmitry Torokhov return NULL; 1401628329d5SDmitry Torokhov } 1402628329d5SDmitry Torokhov 1403628329d5SDmitry Torokhov if (!buflen) { 1404628329d5SDmitry Torokhov dev_err(&intf->dev, "Zero length descriptor\n"); 1405628329d5SDmitry Torokhov return NULL; 1406628329d5SDmitry Torokhov } 1407628329d5SDmitry Torokhov 1408628329d5SDmitry Torokhov while (buflen > 0) { 1409628329d5SDmitry Torokhov union_desc = (struct usb_cdc_union_desc *)buf; 1410628329d5SDmitry Torokhov 1411628329d5SDmitry Torokhov if (union_desc->bDescriptorType == USB_DT_CS_INTERFACE && 1412628329d5SDmitry Torokhov union_desc->bDescriptorSubType == USB_CDC_UNION_TYPE) { 1413628329d5SDmitry Torokhov dev_dbg(&intf->dev, "Found union header\n"); 1414628329d5SDmitry Torokhov return union_desc; 1415628329d5SDmitry Torokhov } 1416628329d5SDmitry Torokhov 1417628329d5SDmitry Torokhov buflen -= union_desc->bLength; 1418628329d5SDmitry Torokhov buf += union_desc->bLength; 1419628329d5SDmitry Torokhov } 1420628329d5SDmitry Torokhov 1421628329d5SDmitry Torokhov dev_err(&intf->dev, "Missing CDC union descriptor\n"); 1422628329d5SDmitry Torokhov return NULL; 1423628329d5SDmitry Torokhov } 1424628329d5SDmitry Torokhov 1425628329d5SDmitry Torokhov static int ims_pcu_parse_cdc_data(struct usb_interface *intf, struct ims_pcu *pcu) 1426628329d5SDmitry Torokhov { 1427628329d5SDmitry Torokhov const struct usb_cdc_union_desc *union_desc; 1428628329d5SDmitry Torokhov struct usb_host_interface *alt; 1429628329d5SDmitry Torokhov 1430628329d5SDmitry Torokhov union_desc = ims_pcu_get_cdc_union_desc(intf); 1431628329d5SDmitry Torokhov if (!union_desc) 1432628329d5SDmitry Torokhov return -EINVAL; 1433628329d5SDmitry Torokhov 1434628329d5SDmitry Torokhov pcu->ctrl_intf = usb_ifnum_to_if(pcu->udev, 1435628329d5SDmitry Torokhov union_desc->bMasterInterface0); 1436628329d5SDmitry Torokhov 1437628329d5SDmitry Torokhov alt = pcu->ctrl_intf->cur_altsetting; 1438628329d5SDmitry Torokhov pcu->ep_ctrl = &alt->endpoint[0].desc; 1439628329d5SDmitry Torokhov pcu->max_ctrl_size = usb_endpoint_maxp(pcu->ep_ctrl); 1440628329d5SDmitry Torokhov 1441628329d5SDmitry Torokhov pcu->data_intf = usb_ifnum_to_if(pcu->udev, 1442628329d5SDmitry Torokhov union_desc->bSlaveInterface0); 1443628329d5SDmitry Torokhov 1444628329d5SDmitry Torokhov alt = pcu->data_intf->cur_altsetting; 1445628329d5SDmitry Torokhov if (alt->desc.bNumEndpoints != 2) { 1446628329d5SDmitry Torokhov dev_err(pcu->dev, 1447628329d5SDmitry Torokhov "Incorrect number of endpoints on data interface (%d)\n", 1448628329d5SDmitry Torokhov alt->desc.bNumEndpoints); 1449628329d5SDmitry Torokhov return -EINVAL; 1450628329d5SDmitry Torokhov } 1451628329d5SDmitry Torokhov 1452628329d5SDmitry Torokhov pcu->ep_out = &alt->endpoint[0].desc; 1453628329d5SDmitry Torokhov if (!usb_endpoint_is_bulk_out(pcu->ep_out)) { 1454628329d5SDmitry Torokhov dev_err(pcu->dev, 1455628329d5SDmitry Torokhov "First endpoint on data interface is not BULK OUT\n"); 1456628329d5SDmitry Torokhov return -EINVAL; 1457628329d5SDmitry Torokhov } 1458628329d5SDmitry Torokhov 1459628329d5SDmitry Torokhov pcu->max_out_size = usb_endpoint_maxp(pcu->ep_out); 1460628329d5SDmitry Torokhov if (pcu->max_out_size < 8) { 1461628329d5SDmitry Torokhov dev_err(pcu->dev, 1462628329d5SDmitry Torokhov "Max OUT packet size is too small (%zd)\n", 1463628329d5SDmitry Torokhov pcu->max_out_size); 1464628329d5SDmitry Torokhov return -EINVAL; 1465628329d5SDmitry Torokhov } 1466628329d5SDmitry Torokhov 1467628329d5SDmitry Torokhov pcu->ep_in = &alt->endpoint[1].desc; 1468628329d5SDmitry Torokhov if (!usb_endpoint_is_bulk_in(pcu->ep_in)) { 1469628329d5SDmitry Torokhov dev_err(pcu->dev, 1470628329d5SDmitry Torokhov "Second endpoint on data interface is not BULK IN\n"); 1471628329d5SDmitry Torokhov return -EINVAL; 1472628329d5SDmitry Torokhov } 1473628329d5SDmitry Torokhov 1474628329d5SDmitry Torokhov pcu->max_in_size = usb_endpoint_maxp(pcu->ep_in); 1475628329d5SDmitry Torokhov if (pcu->max_in_size < 8) { 1476628329d5SDmitry Torokhov dev_err(pcu->dev, 1477628329d5SDmitry Torokhov "Max IN packet size is too small (%zd)\n", 1478628329d5SDmitry Torokhov pcu->max_in_size); 1479628329d5SDmitry Torokhov return -EINVAL; 1480628329d5SDmitry Torokhov } 1481628329d5SDmitry Torokhov 1482628329d5SDmitry Torokhov return 0; 1483628329d5SDmitry Torokhov } 1484628329d5SDmitry Torokhov 1485628329d5SDmitry Torokhov static int ims_pcu_start_io(struct ims_pcu *pcu) 1486628329d5SDmitry Torokhov { 1487628329d5SDmitry Torokhov int error; 1488628329d5SDmitry Torokhov 1489628329d5SDmitry Torokhov error = usb_submit_urb(pcu->urb_ctrl, GFP_KERNEL); 1490628329d5SDmitry Torokhov if (error) { 1491628329d5SDmitry Torokhov dev_err(pcu->dev, 1492628329d5SDmitry Torokhov "Failed to start control IO - usb_submit_urb failed with result: %d\n", 1493628329d5SDmitry Torokhov error); 1494628329d5SDmitry Torokhov return -EIO; 1495628329d5SDmitry Torokhov } 1496628329d5SDmitry Torokhov 1497628329d5SDmitry Torokhov error = usb_submit_urb(pcu->urb_in, GFP_KERNEL); 1498628329d5SDmitry Torokhov if (error) { 1499628329d5SDmitry Torokhov dev_err(pcu->dev, 1500628329d5SDmitry Torokhov "Failed to start IO - usb_submit_urb failed with result: %d\n", 1501628329d5SDmitry Torokhov error); 1502628329d5SDmitry Torokhov usb_kill_urb(pcu->urb_ctrl); 1503628329d5SDmitry Torokhov return -EIO; 1504628329d5SDmitry Torokhov } 1505628329d5SDmitry Torokhov 1506628329d5SDmitry Torokhov return 0; 1507628329d5SDmitry Torokhov } 1508628329d5SDmitry Torokhov 1509628329d5SDmitry Torokhov static void ims_pcu_stop_io(struct ims_pcu *pcu) 1510628329d5SDmitry Torokhov { 1511628329d5SDmitry Torokhov usb_kill_urb(pcu->urb_in); 1512628329d5SDmitry Torokhov usb_kill_urb(pcu->urb_ctrl); 1513628329d5SDmitry Torokhov } 1514628329d5SDmitry Torokhov 1515628329d5SDmitry Torokhov static int ims_pcu_line_setup(struct ims_pcu *pcu) 1516628329d5SDmitry Torokhov { 1517628329d5SDmitry Torokhov struct usb_host_interface *interface = pcu->ctrl_intf->cur_altsetting; 1518628329d5SDmitry Torokhov struct usb_cdc_line_coding *line = (void *)pcu->cmd_buf; 1519628329d5SDmitry Torokhov int error; 1520628329d5SDmitry Torokhov 1521628329d5SDmitry Torokhov memset(line, 0, sizeof(*line)); 1522628329d5SDmitry Torokhov line->dwDTERate = cpu_to_le32(57600); 1523628329d5SDmitry Torokhov line->bDataBits = 8; 1524628329d5SDmitry Torokhov 1525628329d5SDmitry Torokhov error = usb_control_msg(pcu->udev, usb_sndctrlpipe(pcu->udev, 0), 1526628329d5SDmitry Torokhov USB_CDC_REQ_SET_LINE_CODING, 1527628329d5SDmitry Torokhov USB_TYPE_CLASS | USB_RECIP_INTERFACE, 1528628329d5SDmitry Torokhov 0, interface->desc.bInterfaceNumber, 1529628329d5SDmitry Torokhov line, sizeof(struct usb_cdc_line_coding), 1530628329d5SDmitry Torokhov 5000); 1531628329d5SDmitry Torokhov if (error < 0) { 1532628329d5SDmitry Torokhov dev_err(pcu->dev, "Failed to set line coding, error: %d\n", 1533628329d5SDmitry Torokhov error); 1534628329d5SDmitry Torokhov return error; 1535628329d5SDmitry Torokhov } 1536628329d5SDmitry Torokhov 1537628329d5SDmitry Torokhov error = usb_control_msg(pcu->udev, usb_sndctrlpipe(pcu->udev, 0), 1538628329d5SDmitry Torokhov USB_CDC_REQ_SET_CONTROL_LINE_STATE, 1539628329d5SDmitry Torokhov USB_TYPE_CLASS | USB_RECIP_INTERFACE, 1540628329d5SDmitry Torokhov 0x03, interface->desc.bInterfaceNumber, 1541628329d5SDmitry Torokhov NULL, 0, 5000); 1542628329d5SDmitry Torokhov if (error < 0) { 1543628329d5SDmitry Torokhov dev_err(pcu->dev, "Failed to set line state, error: %d\n", 1544628329d5SDmitry Torokhov error); 1545628329d5SDmitry Torokhov return error; 1546628329d5SDmitry Torokhov } 1547628329d5SDmitry Torokhov 1548628329d5SDmitry Torokhov return 0; 1549628329d5SDmitry Torokhov } 1550628329d5SDmitry Torokhov 1551628329d5SDmitry Torokhov static int ims_pcu_get_device_info(struct ims_pcu *pcu) 1552628329d5SDmitry Torokhov { 1553628329d5SDmitry Torokhov int error; 1554628329d5SDmitry Torokhov 1555628329d5SDmitry Torokhov error = ims_pcu_get_info(pcu); 1556628329d5SDmitry Torokhov if (error) 1557628329d5SDmitry Torokhov return error; 1558628329d5SDmitry Torokhov 1559628329d5SDmitry Torokhov error = ims_pcu_execute_query(pcu, GET_FW_VERSION); 1560628329d5SDmitry Torokhov if (error) { 1561628329d5SDmitry Torokhov dev_err(pcu->dev, 1562628329d5SDmitry Torokhov "GET_FW_VERSION command failed, error: %d\n", error); 1563628329d5SDmitry Torokhov return error; 1564628329d5SDmitry Torokhov } 1565628329d5SDmitry Torokhov 1566628329d5SDmitry Torokhov snprintf(pcu->fw_version, sizeof(pcu->fw_version), 1567628329d5SDmitry Torokhov "%02d%02d%02d%02d.%c%c", 1568628329d5SDmitry Torokhov pcu->cmd_buf[2], pcu->cmd_buf[3], pcu->cmd_buf[4], pcu->cmd_buf[5], 1569628329d5SDmitry Torokhov pcu->cmd_buf[6], pcu->cmd_buf[7]); 1570628329d5SDmitry Torokhov 1571628329d5SDmitry Torokhov error = ims_pcu_execute_query(pcu, GET_BL_VERSION); 1572628329d5SDmitry Torokhov if (error) { 1573628329d5SDmitry Torokhov dev_err(pcu->dev, 1574628329d5SDmitry Torokhov "GET_BL_VERSION command failed, error: %d\n", error); 1575628329d5SDmitry Torokhov return error; 1576628329d5SDmitry Torokhov } 1577628329d5SDmitry Torokhov 1578628329d5SDmitry Torokhov snprintf(pcu->bl_version, sizeof(pcu->bl_version), 1579628329d5SDmitry Torokhov "%02d%02d%02d%02d.%c%c", 1580628329d5SDmitry Torokhov pcu->cmd_buf[2], pcu->cmd_buf[3], pcu->cmd_buf[4], pcu->cmd_buf[5], 1581628329d5SDmitry Torokhov pcu->cmd_buf[6], pcu->cmd_buf[7]); 1582628329d5SDmitry Torokhov 1583628329d5SDmitry Torokhov error = ims_pcu_execute_query(pcu, RESET_REASON); 1584628329d5SDmitry Torokhov if (error) { 1585628329d5SDmitry Torokhov dev_err(pcu->dev, 1586628329d5SDmitry Torokhov "RESET_REASON command failed, error: %d\n", error); 1587628329d5SDmitry Torokhov return error; 1588628329d5SDmitry Torokhov } 1589628329d5SDmitry Torokhov 1590628329d5SDmitry Torokhov snprintf(pcu->reset_reason, sizeof(pcu->reset_reason), 1591628329d5SDmitry Torokhov "%02x", pcu->cmd_buf[IMS_PCU_DATA_OFFSET]); 1592628329d5SDmitry Torokhov 1593628329d5SDmitry Torokhov dev_dbg(pcu->dev, 1594628329d5SDmitry Torokhov "P/N: %s, MD: %s, S/N: %s, FW: %s, BL: %s, RR: %s\n", 1595628329d5SDmitry Torokhov pcu->part_number, 1596628329d5SDmitry Torokhov pcu->date_of_manufacturing, 1597628329d5SDmitry Torokhov pcu->serial_number, 1598628329d5SDmitry Torokhov pcu->fw_version, 1599628329d5SDmitry Torokhov pcu->bl_version, 1600628329d5SDmitry Torokhov pcu->reset_reason); 1601628329d5SDmitry Torokhov 1602628329d5SDmitry Torokhov return 0; 1603628329d5SDmitry Torokhov } 1604628329d5SDmitry Torokhov 1605628329d5SDmitry Torokhov static int ims_pcu_identify_type(struct ims_pcu *pcu, u8 *device_id) 1606628329d5SDmitry Torokhov { 1607628329d5SDmitry Torokhov int error; 1608628329d5SDmitry Torokhov 1609628329d5SDmitry Torokhov error = ims_pcu_execute_query(pcu, GET_DEVICE_ID); 1610628329d5SDmitry Torokhov if (error) { 1611628329d5SDmitry Torokhov dev_err(pcu->dev, 1612628329d5SDmitry Torokhov "GET_DEVICE_ID command failed, error: %d\n", error); 1613628329d5SDmitry Torokhov return error; 1614628329d5SDmitry Torokhov } 1615628329d5SDmitry Torokhov 1616628329d5SDmitry Torokhov *device_id = pcu->cmd_buf[IMS_PCU_DATA_OFFSET]; 1617628329d5SDmitry Torokhov dev_dbg(pcu->dev, "Detected device ID: %d\n", *device_id); 1618628329d5SDmitry Torokhov 1619628329d5SDmitry Torokhov return 0; 1620628329d5SDmitry Torokhov } 1621628329d5SDmitry Torokhov 1622628329d5SDmitry Torokhov static int ims_pcu_init_application_mode(struct ims_pcu *pcu) 1623628329d5SDmitry Torokhov { 1624628329d5SDmitry Torokhov static atomic_t device_no = ATOMIC_INIT(0); 1625628329d5SDmitry Torokhov 1626628329d5SDmitry Torokhov const struct ims_pcu_device_info *info; 1627628329d5SDmitry Torokhov u8 device_id; 1628628329d5SDmitry Torokhov int error; 1629628329d5SDmitry Torokhov 1630628329d5SDmitry Torokhov error = ims_pcu_get_device_info(pcu); 1631628329d5SDmitry Torokhov if (error) { 1632628329d5SDmitry Torokhov /* Device does not respond to basic queries, hopeless */ 1633628329d5SDmitry Torokhov return error; 1634628329d5SDmitry Torokhov } 1635628329d5SDmitry Torokhov 1636628329d5SDmitry Torokhov error = ims_pcu_identify_type(pcu, &device_id); 1637628329d5SDmitry Torokhov if (error) { 1638628329d5SDmitry Torokhov dev_err(pcu->dev, 1639628329d5SDmitry Torokhov "Failed to identify device, error: %d\n", error); 1640628329d5SDmitry Torokhov /* 1641628329d5SDmitry Torokhov * Do not signal error, but do not create input nor 1642628329d5SDmitry Torokhov * backlight devices either, let userspace figure this 1643628329d5SDmitry Torokhov * out (flash a new firmware?). 1644628329d5SDmitry Torokhov */ 1645628329d5SDmitry Torokhov return 0; 1646628329d5SDmitry Torokhov } 1647628329d5SDmitry Torokhov 1648628329d5SDmitry Torokhov if (device_id >= ARRAY_SIZE(ims_pcu_device_info) || 1649628329d5SDmitry Torokhov !ims_pcu_device_info[device_id].keymap) { 1650628329d5SDmitry Torokhov dev_err(pcu->dev, "Device ID %d is not valid\n", device_id); 1651628329d5SDmitry Torokhov /* Same as above, punt to userspace */ 1652628329d5SDmitry Torokhov return 0; 1653628329d5SDmitry Torokhov } 1654628329d5SDmitry Torokhov 1655628329d5SDmitry Torokhov /* Device appears to be operable, complete initialization */ 1656628329d5SDmitry Torokhov pcu->device_no = atomic_inc_return(&device_no) - 1; 1657628329d5SDmitry Torokhov 1658628329d5SDmitry Torokhov error = ims_pcu_setup_backlight(pcu); 1659628329d5SDmitry Torokhov if (error) 1660628329d5SDmitry Torokhov return error; 1661628329d5SDmitry Torokhov 1662628329d5SDmitry Torokhov info = &ims_pcu_device_info[device_id]; 1663628329d5SDmitry Torokhov error = ims_pcu_setup_buttons(pcu, info->keymap, info->keymap_len); 1664628329d5SDmitry Torokhov if (error) 1665628329d5SDmitry Torokhov goto err_destroy_backlight; 1666628329d5SDmitry Torokhov 1667628329d5SDmitry Torokhov if (info->has_gamepad) { 1668628329d5SDmitry Torokhov error = ims_pcu_setup_gamepad(pcu); 1669628329d5SDmitry Torokhov if (error) 1670628329d5SDmitry Torokhov goto err_destroy_buttons; 1671628329d5SDmitry Torokhov } 1672628329d5SDmitry Torokhov 1673628329d5SDmitry Torokhov pcu->setup_complete = true; 1674628329d5SDmitry Torokhov 1675628329d5SDmitry Torokhov return 0; 1676628329d5SDmitry Torokhov 1677628329d5SDmitry Torokhov err_destroy_backlight: 1678628329d5SDmitry Torokhov ims_pcu_destroy_backlight(pcu); 1679628329d5SDmitry Torokhov err_destroy_buttons: 1680628329d5SDmitry Torokhov ims_pcu_destroy_buttons(pcu); 1681628329d5SDmitry Torokhov return error; 1682628329d5SDmitry Torokhov } 1683628329d5SDmitry Torokhov 1684628329d5SDmitry Torokhov static void ims_pcu_destroy_application_mode(struct ims_pcu *pcu) 1685628329d5SDmitry Torokhov { 1686628329d5SDmitry Torokhov if (pcu->setup_complete) { 1687628329d5SDmitry Torokhov pcu->setup_complete = false; 1688628329d5SDmitry Torokhov mb(); /* make sure flag setting is not reordered */ 1689628329d5SDmitry Torokhov 1690628329d5SDmitry Torokhov if (pcu->gamepad) 1691628329d5SDmitry Torokhov ims_pcu_destroy_gamepad(pcu); 1692628329d5SDmitry Torokhov ims_pcu_destroy_buttons(pcu); 1693628329d5SDmitry Torokhov ims_pcu_destroy_backlight(pcu); 1694628329d5SDmitry Torokhov } 1695628329d5SDmitry Torokhov } 1696628329d5SDmitry Torokhov 1697628329d5SDmitry Torokhov static int ims_pcu_init_bootloader_mode(struct ims_pcu *pcu) 1698628329d5SDmitry Torokhov { 1699628329d5SDmitry Torokhov int error; 1700628329d5SDmitry Torokhov 1701628329d5SDmitry Torokhov error = ims_pcu_execute_bl_command(pcu, QUERY_DEVICE, NULL, 0, 1702628329d5SDmitry Torokhov IMS_PCU_CMD_RESPONSE_TIMEOUT); 1703628329d5SDmitry Torokhov if (error) { 1704628329d5SDmitry Torokhov dev_err(pcu->dev, "Bootloader does not respond, aborting\n"); 1705628329d5SDmitry Torokhov return error; 1706628329d5SDmitry Torokhov } 1707628329d5SDmitry Torokhov 1708628329d5SDmitry Torokhov pcu->fw_start_addr = 1709628329d5SDmitry Torokhov get_unaligned_le32(&pcu->cmd_buf[IMS_PCU_DATA_OFFSET + 11]); 1710628329d5SDmitry Torokhov pcu->fw_end_addr = 1711628329d5SDmitry Torokhov get_unaligned_le32(&pcu->cmd_buf[IMS_PCU_DATA_OFFSET + 15]); 1712628329d5SDmitry Torokhov 1713628329d5SDmitry Torokhov dev_info(pcu->dev, 1714628329d5SDmitry Torokhov "Device is in bootloader mode (addr 0x%08x-0x%08x), requesting firmware\n", 1715628329d5SDmitry Torokhov pcu->fw_start_addr, pcu->fw_end_addr); 1716628329d5SDmitry Torokhov 1717628329d5SDmitry Torokhov error = request_firmware_nowait(THIS_MODULE, true, 1718628329d5SDmitry Torokhov IMS_PCU_FIRMWARE_NAME, 1719628329d5SDmitry Torokhov pcu->dev, GFP_KERNEL, pcu, 1720628329d5SDmitry Torokhov ims_pcu_process_async_firmware); 1721628329d5SDmitry Torokhov if (error) { 1722628329d5SDmitry Torokhov /* This error is not fatal, let userspace have another chance */ 1723628329d5SDmitry Torokhov complete(&pcu->async_firmware_done); 1724628329d5SDmitry Torokhov } 1725628329d5SDmitry Torokhov 1726628329d5SDmitry Torokhov return 0; 1727628329d5SDmitry Torokhov } 1728628329d5SDmitry Torokhov 1729628329d5SDmitry Torokhov static void ims_pcu_destroy_bootloader_mode(struct ims_pcu *pcu) 1730628329d5SDmitry Torokhov { 1731628329d5SDmitry Torokhov /* Make sure our initial firmware request has completed */ 1732628329d5SDmitry Torokhov wait_for_completion(&pcu->async_firmware_done); 1733628329d5SDmitry Torokhov } 1734628329d5SDmitry Torokhov 1735628329d5SDmitry Torokhov #define IMS_PCU_APPLICATION_MODE 0 1736628329d5SDmitry Torokhov #define IMS_PCU_BOOTLOADER_MODE 1 1737628329d5SDmitry Torokhov 1738628329d5SDmitry Torokhov static struct usb_driver ims_pcu_driver; 1739628329d5SDmitry Torokhov 1740628329d5SDmitry Torokhov static int ims_pcu_probe(struct usb_interface *intf, 1741628329d5SDmitry Torokhov const struct usb_device_id *id) 1742628329d5SDmitry Torokhov { 1743628329d5SDmitry Torokhov struct usb_device *udev = interface_to_usbdev(intf); 1744628329d5SDmitry Torokhov struct ims_pcu *pcu; 1745628329d5SDmitry Torokhov int error; 1746628329d5SDmitry Torokhov 1747628329d5SDmitry Torokhov pcu = kzalloc(sizeof(struct ims_pcu), GFP_KERNEL); 1748628329d5SDmitry Torokhov if (!pcu) 1749628329d5SDmitry Torokhov return -ENOMEM; 1750628329d5SDmitry Torokhov 1751628329d5SDmitry Torokhov pcu->dev = &intf->dev; 1752628329d5SDmitry Torokhov pcu->udev = udev; 1753628329d5SDmitry Torokhov pcu->bootloader_mode = id->driver_info == IMS_PCU_BOOTLOADER_MODE; 1754628329d5SDmitry Torokhov mutex_init(&pcu->cmd_mutex); 1755628329d5SDmitry Torokhov init_completion(&pcu->cmd_done); 1756628329d5SDmitry Torokhov init_completion(&pcu->async_firmware_done); 1757628329d5SDmitry Torokhov 1758628329d5SDmitry Torokhov error = ims_pcu_parse_cdc_data(intf, pcu); 1759628329d5SDmitry Torokhov if (error) 1760628329d5SDmitry Torokhov goto err_free_mem; 1761628329d5SDmitry Torokhov 1762628329d5SDmitry Torokhov error = usb_driver_claim_interface(&ims_pcu_driver, 1763628329d5SDmitry Torokhov pcu->data_intf, pcu); 1764628329d5SDmitry Torokhov if (error) { 1765628329d5SDmitry Torokhov dev_err(&intf->dev, 1766628329d5SDmitry Torokhov "Unable to claim corresponding data interface: %d\n", 1767628329d5SDmitry Torokhov error); 1768628329d5SDmitry Torokhov goto err_free_mem; 1769628329d5SDmitry Torokhov } 1770628329d5SDmitry Torokhov 1771628329d5SDmitry Torokhov usb_set_intfdata(pcu->ctrl_intf, pcu); 1772628329d5SDmitry Torokhov usb_set_intfdata(pcu->data_intf, pcu); 1773628329d5SDmitry Torokhov 1774628329d5SDmitry Torokhov error = ims_pcu_buffers_alloc(pcu); 1775628329d5SDmitry Torokhov if (error) 1776628329d5SDmitry Torokhov goto err_unclaim_intf; 1777628329d5SDmitry Torokhov 1778628329d5SDmitry Torokhov error = ims_pcu_start_io(pcu); 1779628329d5SDmitry Torokhov if (error) 1780628329d5SDmitry Torokhov goto err_free_buffers; 1781628329d5SDmitry Torokhov 1782628329d5SDmitry Torokhov error = ims_pcu_line_setup(pcu); 1783628329d5SDmitry Torokhov if (error) 1784628329d5SDmitry Torokhov goto err_stop_io; 1785628329d5SDmitry Torokhov 1786628329d5SDmitry Torokhov error = sysfs_create_group(&intf->dev.kobj, &ims_pcu_attr_group); 1787628329d5SDmitry Torokhov if (error) 1788628329d5SDmitry Torokhov goto err_stop_io; 1789628329d5SDmitry Torokhov 1790628329d5SDmitry Torokhov error = pcu->bootloader_mode ? 1791628329d5SDmitry Torokhov ims_pcu_init_bootloader_mode(pcu) : 1792628329d5SDmitry Torokhov ims_pcu_init_application_mode(pcu); 1793628329d5SDmitry Torokhov if (error) 1794628329d5SDmitry Torokhov goto err_remove_sysfs; 1795628329d5SDmitry Torokhov 1796628329d5SDmitry Torokhov return 0; 1797628329d5SDmitry Torokhov 1798628329d5SDmitry Torokhov err_remove_sysfs: 1799628329d5SDmitry Torokhov sysfs_remove_group(&intf->dev.kobj, &ims_pcu_attr_group); 1800628329d5SDmitry Torokhov err_stop_io: 1801628329d5SDmitry Torokhov ims_pcu_stop_io(pcu); 1802628329d5SDmitry Torokhov err_free_buffers: 1803628329d5SDmitry Torokhov ims_pcu_buffers_free(pcu); 1804628329d5SDmitry Torokhov err_unclaim_intf: 1805628329d5SDmitry Torokhov usb_driver_release_interface(&ims_pcu_driver, pcu->data_intf); 1806628329d5SDmitry Torokhov err_free_mem: 1807628329d5SDmitry Torokhov kfree(pcu); 1808628329d5SDmitry Torokhov return error; 1809628329d5SDmitry Torokhov } 1810628329d5SDmitry Torokhov 1811628329d5SDmitry Torokhov static void ims_pcu_disconnect(struct usb_interface *intf) 1812628329d5SDmitry Torokhov { 1813628329d5SDmitry Torokhov struct ims_pcu *pcu = usb_get_intfdata(intf); 1814628329d5SDmitry Torokhov struct usb_host_interface *alt = intf->cur_altsetting; 1815628329d5SDmitry Torokhov 1816628329d5SDmitry Torokhov usb_set_intfdata(intf, NULL); 1817628329d5SDmitry Torokhov 1818628329d5SDmitry Torokhov /* 1819628329d5SDmitry Torokhov * See if we are dealing with control or data interface. The cleanup 1820628329d5SDmitry Torokhov * happens when we unbind primary (control) interface. 1821628329d5SDmitry Torokhov */ 1822628329d5SDmitry Torokhov if (alt->desc.bInterfaceClass != USB_CLASS_COMM) 1823628329d5SDmitry Torokhov return; 1824628329d5SDmitry Torokhov 1825628329d5SDmitry Torokhov sysfs_remove_group(&intf->dev.kobj, &ims_pcu_attr_group); 1826628329d5SDmitry Torokhov 1827628329d5SDmitry Torokhov ims_pcu_stop_io(pcu); 1828628329d5SDmitry Torokhov 1829628329d5SDmitry Torokhov if (pcu->bootloader_mode) 1830628329d5SDmitry Torokhov ims_pcu_destroy_bootloader_mode(pcu); 1831628329d5SDmitry Torokhov else 1832628329d5SDmitry Torokhov ims_pcu_destroy_application_mode(pcu); 1833628329d5SDmitry Torokhov 1834628329d5SDmitry Torokhov ims_pcu_buffers_free(pcu); 1835628329d5SDmitry Torokhov kfree(pcu); 1836628329d5SDmitry Torokhov } 1837628329d5SDmitry Torokhov 1838628329d5SDmitry Torokhov #ifdef CONFIG_PM 1839628329d5SDmitry Torokhov static int ims_pcu_suspend(struct usb_interface *intf, 1840628329d5SDmitry Torokhov pm_message_t message) 1841628329d5SDmitry Torokhov { 1842628329d5SDmitry Torokhov struct ims_pcu *pcu = usb_get_intfdata(intf); 1843628329d5SDmitry Torokhov struct usb_host_interface *alt = intf->cur_altsetting; 1844628329d5SDmitry Torokhov 1845628329d5SDmitry Torokhov if (alt->desc.bInterfaceClass == USB_CLASS_COMM) 1846628329d5SDmitry Torokhov ims_pcu_stop_io(pcu); 1847628329d5SDmitry Torokhov 1848628329d5SDmitry Torokhov return 0; 1849628329d5SDmitry Torokhov } 1850628329d5SDmitry Torokhov 1851628329d5SDmitry Torokhov static int ims_pcu_resume(struct usb_interface *intf) 1852628329d5SDmitry Torokhov { 1853628329d5SDmitry Torokhov struct ims_pcu *pcu = usb_get_intfdata(intf); 1854628329d5SDmitry Torokhov struct usb_host_interface *alt = intf->cur_altsetting; 1855628329d5SDmitry Torokhov int retval = 0; 1856628329d5SDmitry Torokhov 1857628329d5SDmitry Torokhov if (alt->desc.bInterfaceClass == USB_CLASS_COMM) { 1858628329d5SDmitry Torokhov retval = ims_pcu_start_io(pcu); 1859628329d5SDmitry Torokhov if (retval == 0) 1860628329d5SDmitry Torokhov retval = ims_pcu_line_setup(pcu); 1861628329d5SDmitry Torokhov } 1862628329d5SDmitry Torokhov 1863628329d5SDmitry Torokhov return retval; 1864628329d5SDmitry Torokhov } 1865628329d5SDmitry Torokhov #endif 1866628329d5SDmitry Torokhov 1867628329d5SDmitry Torokhov static const struct usb_device_id ims_pcu_id_table[] = { 1868628329d5SDmitry Torokhov { 1869628329d5SDmitry Torokhov USB_DEVICE_AND_INTERFACE_INFO(0x04d8, 0x0082, 1870628329d5SDmitry Torokhov USB_CLASS_COMM, 1871628329d5SDmitry Torokhov USB_CDC_SUBCLASS_ACM, 1872628329d5SDmitry Torokhov USB_CDC_ACM_PROTO_AT_V25TER), 1873628329d5SDmitry Torokhov .driver_info = IMS_PCU_APPLICATION_MODE, 1874628329d5SDmitry Torokhov }, 1875628329d5SDmitry Torokhov { 1876628329d5SDmitry Torokhov USB_DEVICE_AND_INTERFACE_INFO(0x04d8, 0x0083, 1877628329d5SDmitry Torokhov USB_CLASS_COMM, 1878628329d5SDmitry Torokhov USB_CDC_SUBCLASS_ACM, 1879628329d5SDmitry Torokhov USB_CDC_ACM_PROTO_AT_V25TER), 1880628329d5SDmitry Torokhov .driver_info = IMS_PCU_BOOTLOADER_MODE, 1881628329d5SDmitry Torokhov }, 1882628329d5SDmitry Torokhov { } 1883628329d5SDmitry Torokhov }; 1884628329d5SDmitry Torokhov 1885628329d5SDmitry Torokhov static struct usb_driver ims_pcu_driver = { 1886628329d5SDmitry Torokhov .name = "ims_pcu", 1887628329d5SDmitry Torokhov .id_table = ims_pcu_id_table, 1888628329d5SDmitry Torokhov .probe = ims_pcu_probe, 1889628329d5SDmitry Torokhov .disconnect = ims_pcu_disconnect, 1890628329d5SDmitry Torokhov #ifdef CONFIG_PM 1891628329d5SDmitry Torokhov .suspend = ims_pcu_suspend, 1892628329d5SDmitry Torokhov .resume = ims_pcu_resume, 1893628329d5SDmitry Torokhov .reset_resume = ims_pcu_resume, 1894628329d5SDmitry Torokhov #endif 1895628329d5SDmitry Torokhov }; 1896628329d5SDmitry Torokhov 1897628329d5SDmitry Torokhov module_usb_driver(ims_pcu_driver); 1898628329d5SDmitry Torokhov 1899628329d5SDmitry Torokhov MODULE_DESCRIPTION("IMS Passenger Control Unit driver"); 1900628329d5SDmitry Torokhov MODULE_AUTHOR("Dmitry Torokhov <dmitry.torokhov@gmail.com>"); 1901628329d5SDmitry Torokhov MODULE_LICENSE("GPL"); 1902