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 54e5fcd269SAndrey Smirnov #define IMS_PCU_PCU_B_DEVICE_ID 5 55e5fcd269SAndrey Smirnov 56628329d5SDmitry Torokhov #define IMS_PCU_BUF_SIZE 128 57628329d5SDmitry Torokhov 58628329d5SDmitry Torokhov struct ims_pcu { 59628329d5SDmitry Torokhov struct usb_device *udev; 60628329d5SDmitry Torokhov struct device *dev; /* control interface's device, used for logging */ 61628329d5SDmitry Torokhov 62628329d5SDmitry Torokhov unsigned int device_no; 63628329d5SDmitry Torokhov 64628329d5SDmitry Torokhov bool bootloader_mode; 65628329d5SDmitry Torokhov 66628329d5SDmitry Torokhov char part_number[IMS_PCU_PART_NUMBER_LEN]; 67628329d5SDmitry Torokhov char serial_number[IMS_PCU_SERIAL_NUMBER_LEN]; 68628329d5SDmitry Torokhov char date_of_manufacturing[IMS_PCU_DOM_LEN]; 69628329d5SDmitry Torokhov char fw_version[IMS_PCU_FW_VERSION_LEN]; 70628329d5SDmitry Torokhov char bl_version[IMS_PCU_BL_VERSION_LEN]; 71628329d5SDmitry Torokhov char reset_reason[IMS_PCU_BL_RESET_REASON_LEN]; 72628329d5SDmitry Torokhov int update_firmware_status; 73e5fcd269SAndrey Smirnov u8 device_id; 74e5fcd269SAndrey Smirnov 75e5fcd269SAndrey Smirnov u8 ofn_reg_addr; 76628329d5SDmitry Torokhov 77628329d5SDmitry Torokhov struct usb_interface *ctrl_intf; 78628329d5SDmitry Torokhov 79628329d5SDmitry Torokhov struct usb_endpoint_descriptor *ep_ctrl; 80628329d5SDmitry Torokhov struct urb *urb_ctrl; 81628329d5SDmitry Torokhov u8 *urb_ctrl_buf; 82628329d5SDmitry Torokhov dma_addr_t ctrl_dma; 83628329d5SDmitry Torokhov size_t max_ctrl_size; 84628329d5SDmitry Torokhov 85628329d5SDmitry Torokhov struct usb_interface *data_intf; 86628329d5SDmitry Torokhov 87628329d5SDmitry Torokhov struct usb_endpoint_descriptor *ep_in; 88628329d5SDmitry Torokhov struct urb *urb_in; 89628329d5SDmitry Torokhov u8 *urb_in_buf; 90628329d5SDmitry Torokhov dma_addr_t read_dma; 91628329d5SDmitry Torokhov size_t max_in_size; 92628329d5SDmitry Torokhov 93628329d5SDmitry Torokhov struct usb_endpoint_descriptor *ep_out; 94628329d5SDmitry Torokhov u8 *urb_out_buf; 95628329d5SDmitry Torokhov size_t max_out_size; 96628329d5SDmitry Torokhov 97628329d5SDmitry Torokhov u8 read_buf[IMS_PCU_BUF_SIZE]; 98628329d5SDmitry Torokhov u8 read_pos; 99628329d5SDmitry Torokhov u8 check_sum; 100628329d5SDmitry Torokhov bool have_stx; 101628329d5SDmitry Torokhov bool have_dle; 102628329d5SDmitry Torokhov 103628329d5SDmitry Torokhov u8 cmd_buf[IMS_PCU_BUF_SIZE]; 104628329d5SDmitry Torokhov u8 ack_id; 105628329d5SDmitry Torokhov u8 expected_response; 106628329d5SDmitry Torokhov u8 cmd_buf_len; 107628329d5SDmitry Torokhov struct completion cmd_done; 108628329d5SDmitry Torokhov struct mutex cmd_mutex; 109628329d5SDmitry Torokhov 110628329d5SDmitry Torokhov u32 fw_start_addr; 111628329d5SDmitry Torokhov u32 fw_end_addr; 112628329d5SDmitry Torokhov struct completion async_firmware_done; 113628329d5SDmitry Torokhov 114628329d5SDmitry Torokhov struct ims_pcu_buttons buttons; 115628329d5SDmitry Torokhov struct ims_pcu_gamepad *gamepad; 116628329d5SDmitry Torokhov struct ims_pcu_backlight backlight; 117628329d5SDmitry Torokhov 118628329d5SDmitry Torokhov bool setup_complete; /* Input and LED devices have been created */ 119628329d5SDmitry Torokhov }; 120628329d5SDmitry Torokhov 121628329d5SDmitry Torokhov 122628329d5SDmitry Torokhov /********************************************************************* 123628329d5SDmitry Torokhov * Buttons Input device support * 124628329d5SDmitry Torokhov *********************************************************************/ 125628329d5SDmitry Torokhov 126628329d5SDmitry Torokhov static const unsigned short ims_pcu_keymap_1[] = { 127628329d5SDmitry Torokhov [1] = KEY_ATTENDANT_OFF, 128628329d5SDmitry Torokhov [2] = KEY_ATTENDANT_ON, 129628329d5SDmitry Torokhov [3] = KEY_LIGHTS_TOGGLE, 130628329d5SDmitry Torokhov [4] = KEY_VOLUMEUP, 131628329d5SDmitry Torokhov [5] = KEY_VOLUMEDOWN, 132628329d5SDmitry Torokhov [6] = KEY_INFO, 133628329d5SDmitry Torokhov }; 134628329d5SDmitry Torokhov 135628329d5SDmitry Torokhov static const unsigned short ims_pcu_keymap_2[] = { 136628329d5SDmitry Torokhov [4] = KEY_VOLUMEUP, 137628329d5SDmitry Torokhov [5] = KEY_VOLUMEDOWN, 138628329d5SDmitry Torokhov [6] = KEY_INFO, 139628329d5SDmitry Torokhov }; 140628329d5SDmitry Torokhov 141628329d5SDmitry Torokhov static const unsigned short ims_pcu_keymap_3[] = { 142628329d5SDmitry Torokhov [1] = KEY_HOMEPAGE, 143628329d5SDmitry Torokhov [2] = KEY_ATTENDANT_TOGGLE, 144628329d5SDmitry Torokhov [3] = KEY_LIGHTS_TOGGLE, 145628329d5SDmitry Torokhov [4] = KEY_VOLUMEUP, 146628329d5SDmitry Torokhov [5] = KEY_VOLUMEDOWN, 147628329d5SDmitry Torokhov [6] = KEY_DISPLAYTOGGLE, 148628329d5SDmitry Torokhov [18] = KEY_PLAYPAUSE, 149628329d5SDmitry Torokhov }; 150628329d5SDmitry Torokhov 151628329d5SDmitry Torokhov static const unsigned short ims_pcu_keymap_4[] = { 152628329d5SDmitry Torokhov [1] = KEY_ATTENDANT_OFF, 153628329d5SDmitry Torokhov [2] = KEY_ATTENDANT_ON, 154628329d5SDmitry Torokhov [3] = KEY_LIGHTS_TOGGLE, 155628329d5SDmitry Torokhov [4] = KEY_VOLUMEUP, 156628329d5SDmitry Torokhov [5] = KEY_VOLUMEDOWN, 157628329d5SDmitry Torokhov [6] = KEY_INFO, 158628329d5SDmitry Torokhov [18] = KEY_PLAYPAUSE, 159628329d5SDmitry Torokhov }; 160628329d5SDmitry Torokhov 161628329d5SDmitry Torokhov static const unsigned short ims_pcu_keymap_5[] = { 162628329d5SDmitry Torokhov [1] = KEY_ATTENDANT_OFF, 163628329d5SDmitry Torokhov [2] = KEY_ATTENDANT_ON, 164628329d5SDmitry Torokhov [3] = KEY_LIGHTS_TOGGLE, 165628329d5SDmitry Torokhov }; 166628329d5SDmitry Torokhov 167628329d5SDmitry Torokhov struct ims_pcu_device_info { 168628329d5SDmitry Torokhov const unsigned short *keymap; 169628329d5SDmitry Torokhov size_t keymap_len; 170628329d5SDmitry Torokhov bool has_gamepad; 171628329d5SDmitry Torokhov }; 172628329d5SDmitry Torokhov 173628329d5SDmitry Torokhov #define IMS_PCU_DEVINFO(_n, _gamepad) \ 174628329d5SDmitry Torokhov [_n] = { \ 175628329d5SDmitry Torokhov .keymap = ims_pcu_keymap_##_n, \ 176628329d5SDmitry Torokhov .keymap_len = ARRAY_SIZE(ims_pcu_keymap_##_n), \ 177628329d5SDmitry Torokhov .has_gamepad = _gamepad, \ 178628329d5SDmitry Torokhov } 179628329d5SDmitry Torokhov 180628329d5SDmitry Torokhov static const struct ims_pcu_device_info ims_pcu_device_info[] = { 181628329d5SDmitry Torokhov IMS_PCU_DEVINFO(1, true), 182628329d5SDmitry Torokhov IMS_PCU_DEVINFO(2, true), 183628329d5SDmitry Torokhov IMS_PCU_DEVINFO(3, true), 184628329d5SDmitry Torokhov IMS_PCU_DEVINFO(4, true), 185628329d5SDmitry Torokhov IMS_PCU_DEVINFO(5, false), 186628329d5SDmitry Torokhov }; 187628329d5SDmitry Torokhov 188628329d5SDmitry Torokhov static void ims_pcu_buttons_report(struct ims_pcu *pcu, u32 data) 189628329d5SDmitry Torokhov { 190628329d5SDmitry Torokhov struct ims_pcu_buttons *buttons = &pcu->buttons; 191628329d5SDmitry Torokhov struct input_dev *input = buttons->input; 192628329d5SDmitry Torokhov int i; 193628329d5SDmitry Torokhov 194628329d5SDmitry Torokhov for (i = 0; i < 32; i++) { 195628329d5SDmitry Torokhov unsigned short keycode = buttons->keymap[i]; 196628329d5SDmitry Torokhov 197628329d5SDmitry Torokhov if (keycode != KEY_RESERVED) 198628329d5SDmitry Torokhov input_report_key(input, keycode, data & (1UL << i)); 199628329d5SDmitry Torokhov } 200628329d5SDmitry Torokhov 201628329d5SDmitry Torokhov input_sync(input); 202628329d5SDmitry Torokhov } 203628329d5SDmitry Torokhov 204628329d5SDmitry Torokhov static int ims_pcu_setup_buttons(struct ims_pcu *pcu, 205628329d5SDmitry Torokhov const unsigned short *keymap, 206628329d5SDmitry Torokhov size_t keymap_len) 207628329d5SDmitry Torokhov { 208628329d5SDmitry Torokhov struct ims_pcu_buttons *buttons = &pcu->buttons; 209628329d5SDmitry Torokhov struct input_dev *input; 210628329d5SDmitry Torokhov int i; 211628329d5SDmitry Torokhov int error; 212628329d5SDmitry Torokhov 213628329d5SDmitry Torokhov input = input_allocate_device(); 214628329d5SDmitry Torokhov if (!input) { 215628329d5SDmitry Torokhov dev_err(pcu->dev, 216628329d5SDmitry Torokhov "Not enough memory for input input device\n"); 217628329d5SDmitry Torokhov return -ENOMEM; 218628329d5SDmitry Torokhov } 219628329d5SDmitry Torokhov 220628329d5SDmitry Torokhov snprintf(buttons->name, sizeof(buttons->name), 221628329d5SDmitry Torokhov "IMS PCU#%d Button Interface", pcu->device_no); 222628329d5SDmitry Torokhov 223628329d5SDmitry Torokhov usb_make_path(pcu->udev, buttons->phys, sizeof(buttons->phys)); 224628329d5SDmitry Torokhov strlcat(buttons->phys, "/input0", sizeof(buttons->phys)); 225628329d5SDmitry Torokhov 226628329d5SDmitry Torokhov memcpy(buttons->keymap, keymap, sizeof(*keymap) * keymap_len); 227628329d5SDmitry Torokhov 228628329d5SDmitry Torokhov input->name = buttons->name; 229628329d5SDmitry Torokhov input->phys = buttons->phys; 230628329d5SDmitry Torokhov usb_to_input_id(pcu->udev, &input->id); 231628329d5SDmitry Torokhov input->dev.parent = &pcu->ctrl_intf->dev; 232628329d5SDmitry Torokhov 233628329d5SDmitry Torokhov input->keycode = buttons->keymap; 234628329d5SDmitry Torokhov input->keycodemax = ARRAY_SIZE(buttons->keymap); 235628329d5SDmitry Torokhov input->keycodesize = sizeof(buttons->keymap[0]); 236628329d5SDmitry Torokhov 237628329d5SDmitry Torokhov __set_bit(EV_KEY, input->evbit); 238628329d5SDmitry Torokhov for (i = 0; i < IMS_PCU_KEYMAP_LEN; i++) 239628329d5SDmitry Torokhov __set_bit(buttons->keymap[i], input->keybit); 240628329d5SDmitry Torokhov __clear_bit(KEY_RESERVED, input->keybit); 241628329d5SDmitry Torokhov 242628329d5SDmitry Torokhov error = input_register_device(input); 243628329d5SDmitry Torokhov if (error) { 244628329d5SDmitry Torokhov dev_err(pcu->dev, 245628329d5SDmitry Torokhov "Failed to register buttons input device: %d\n", 246628329d5SDmitry Torokhov error); 247628329d5SDmitry Torokhov input_free_device(input); 248628329d5SDmitry Torokhov return error; 249628329d5SDmitry Torokhov } 250628329d5SDmitry Torokhov 251628329d5SDmitry Torokhov buttons->input = input; 252628329d5SDmitry Torokhov return 0; 253628329d5SDmitry Torokhov } 254628329d5SDmitry Torokhov 255628329d5SDmitry Torokhov static void ims_pcu_destroy_buttons(struct ims_pcu *pcu) 256628329d5SDmitry Torokhov { 257628329d5SDmitry Torokhov struct ims_pcu_buttons *buttons = &pcu->buttons; 258628329d5SDmitry Torokhov 259628329d5SDmitry Torokhov input_unregister_device(buttons->input); 260628329d5SDmitry Torokhov } 261628329d5SDmitry Torokhov 262628329d5SDmitry Torokhov 263628329d5SDmitry Torokhov /********************************************************************* 264628329d5SDmitry Torokhov * Gamepad Input device support * 265628329d5SDmitry Torokhov *********************************************************************/ 266628329d5SDmitry Torokhov 267628329d5SDmitry Torokhov static void ims_pcu_gamepad_report(struct ims_pcu *pcu, u32 data) 268628329d5SDmitry Torokhov { 269628329d5SDmitry Torokhov struct ims_pcu_gamepad *gamepad = pcu->gamepad; 270628329d5SDmitry Torokhov struct input_dev *input = gamepad->input; 271628329d5SDmitry Torokhov int x, y; 272628329d5SDmitry Torokhov 273628329d5SDmitry Torokhov x = !!(data & (1 << 14)) - !!(data & (1 << 13)); 274628329d5SDmitry Torokhov y = !!(data & (1 << 12)) - !!(data & (1 << 11)); 275628329d5SDmitry Torokhov 276628329d5SDmitry Torokhov input_report_abs(input, ABS_X, x); 277628329d5SDmitry Torokhov input_report_abs(input, ABS_Y, y); 278628329d5SDmitry Torokhov 279628329d5SDmitry Torokhov input_report_key(input, BTN_A, data & (1 << 7)); 280628329d5SDmitry Torokhov input_report_key(input, BTN_B, data & (1 << 8)); 281628329d5SDmitry Torokhov input_report_key(input, BTN_X, data & (1 << 9)); 282628329d5SDmitry Torokhov input_report_key(input, BTN_Y, data & (1 << 10)); 283628329d5SDmitry Torokhov input_report_key(input, BTN_START, data & (1 << 15)); 284628329d5SDmitry Torokhov input_report_key(input, BTN_SELECT, data & (1 << 16)); 285628329d5SDmitry Torokhov 286628329d5SDmitry Torokhov input_sync(input); 287628329d5SDmitry Torokhov } 288628329d5SDmitry Torokhov 289628329d5SDmitry Torokhov static int ims_pcu_setup_gamepad(struct ims_pcu *pcu) 290628329d5SDmitry Torokhov { 291628329d5SDmitry Torokhov struct ims_pcu_gamepad *gamepad; 292628329d5SDmitry Torokhov struct input_dev *input; 293628329d5SDmitry Torokhov int error; 294628329d5SDmitry Torokhov 295628329d5SDmitry Torokhov gamepad = kzalloc(sizeof(struct ims_pcu_gamepad), GFP_KERNEL); 296628329d5SDmitry Torokhov input = input_allocate_device(); 297628329d5SDmitry Torokhov if (!gamepad || !input) { 298628329d5SDmitry Torokhov dev_err(pcu->dev, 299628329d5SDmitry Torokhov "Not enough memory for gamepad device\n"); 3003c2b9010SDmitry Torokhov error = -ENOMEM; 3013c2b9010SDmitry Torokhov goto err_free_mem; 302628329d5SDmitry Torokhov } 303628329d5SDmitry Torokhov 304628329d5SDmitry Torokhov gamepad->input = input; 305628329d5SDmitry Torokhov 306628329d5SDmitry Torokhov snprintf(gamepad->name, sizeof(gamepad->name), 307628329d5SDmitry Torokhov "IMS PCU#%d Gamepad Interface", pcu->device_no); 308628329d5SDmitry Torokhov 309628329d5SDmitry Torokhov usb_make_path(pcu->udev, gamepad->phys, sizeof(gamepad->phys)); 310628329d5SDmitry Torokhov strlcat(gamepad->phys, "/input1", sizeof(gamepad->phys)); 311628329d5SDmitry Torokhov 312628329d5SDmitry Torokhov input->name = gamepad->name; 313628329d5SDmitry Torokhov input->phys = gamepad->phys; 314628329d5SDmitry Torokhov usb_to_input_id(pcu->udev, &input->id); 315628329d5SDmitry Torokhov input->dev.parent = &pcu->ctrl_intf->dev; 316628329d5SDmitry Torokhov 317628329d5SDmitry Torokhov __set_bit(EV_KEY, input->evbit); 318628329d5SDmitry Torokhov __set_bit(BTN_A, input->keybit); 319628329d5SDmitry Torokhov __set_bit(BTN_B, input->keybit); 320628329d5SDmitry Torokhov __set_bit(BTN_X, input->keybit); 321628329d5SDmitry Torokhov __set_bit(BTN_Y, input->keybit); 322628329d5SDmitry Torokhov __set_bit(BTN_START, input->keybit); 323628329d5SDmitry Torokhov __set_bit(BTN_SELECT, input->keybit); 324628329d5SDmitry Torokhov 325628329d5SDmitry Torokhov __set_bit(EV_ABS, input->evbit); 326628329d5SDmitry Torokhov input_set_abs_params(input, ABS_X, -1, 1, 0, 0); 327628329d5SDmitry Torokhov input_set_abs_params(input, ABS_Y, -1, 1, 0, 0); 328628329d5SDmitry Torokhov 329628329d5SDmitry Torokhov error = input_register_device(input); 330628329d5SDmitry Torokhov if (error) { 331628329d5SDmitry Torokhov dev_err(pcu->dev, 332628329d5SDmitry Torokhov "Failed to register gamepad input device: %d\n", 333628329d5SDmitry Torokhov error); 334628329d5SDmitry Torokhov goto err_free_mem; 335628329d5SDmitry Torokhov } 336628329d5SDmitry Torokhov 337628329d5SDmitry Torokhov pcu->gamepad = gamepad; 338628329d5SDmitry Torokhov return 0; 339628329d5SDmitry Torokhov 340628329d5SDmitry Torokhov err_free_mem: 341628329d5SDmitry Torokhov input_free_device(input); 342628329d5SDmitry Torokhov kfree(gamepad); 343628329d5SDmitry Torokhov return -ENOMEM; 344628329d5SDmitry Torokhov } 345628329d5SDmitry Torokhov 346628329d5SDmitry Torokhov static void ims_pcu_destroy_gamepad(struct ims_pcu *pcu) 347628329d5SDmitry Torokhov { 348628329d5SDmitry Torokhov struct ims_pcu_gamepad *gamepad = pcu->gamepad; 349628329d5SDmitry Torokhov 350628329d5SDmitry Torokhov input_unregister_device(gamepad->input); 351628329d5SDmitry Torokhov kfree(gamepad); 352628329d5SDmitry Torokhov } 353628329d5SDmitry Torokhov 354628329d5SDmitry Torokhov 355628329d5SDmitry Torokhov /********************************************************************* 356628329d5SDmitry Torokhov * PCU Communication protocol handling * 357628329d5SDmitry Torokhov *********************************************************************/ 358628329d5SDmitry Torokhov 359628329d5SDmitry Torokhov #define IMS_PCU_PROTOCOL_STX 0x02 360628329d5SDmitry Torokhov #define IMS_PCU_PROTOCOL_ETX 0x03 361628329d5SDmitry Torokhov #define IMS_PCU_PROTOCOL_DLE 0x10 362628329d5SDmitry Torokhov 363628329d5SDmitry Torokhov /* PCU commands */ 364628329d5SDmitry Torokhov #define IMS_PCU_CMD_STATUS 0xa0 365628329d5SDmitry Torokhov #define IMS_PCU_CMD_PCU_RESET 0xa1 366628329d5SDmitry Torokhov #define IMS_PCU_CMD_RESET_REASON 0xa2 367628329d5SDmitry Torokhov #define IMS_PCU_CMD_SEND_BUTTONS 0xa3 368628329d5SDmitry Torokhov #define IMS_PCU_CMD_JUMP_TO_BTLDR 0xa4 369628329d5SDmitry Torokhov #define IMS_PCU_CMD_GET_INFO 0xa5 370628329d5SDmitry Torokhov #define IMS_PCU_CMD_SET_BRIGHTNESS 0xa6 371628329d5SDmitry Torokhov #define IMS_PCU_CMD_EEPROM 0xa7 372628329d5SDmitry Torokhov #define IMS_PCU_CMD_GET_FW_VERSION 0xa8 373628329d5SDmitry Torokhov #define IMS_PCU_CMD_GET_BL_VERSION 0xa9 374628329d5SDmitry Torokhov #define IMS_PCU_CMD_SET_INFO 0xab 375628329d5SDmitry Torokhov #define IMS_PCU_CMD_GET_BRIGHTNESS 0xac 376628329d5SDmitry Torokhov #define IMS_PCU_CMD_GET_DEVICE_ID 0xae 377628329d5SDmitry Torokhov #define IMS_PCU_CMD_SPECIAL_INFO 0xb0 378628329d5SDmitry Torokhov #define IMS_PCU_CMD_BOOTLOADER 0xb1 /* Pass data to bootloader */ 379e5fcd269SAndrey Smirnov #define IMS_PCU_CMD_OFN_SET_CONFIG 0xb3 380e5fcd269SAndrey Smirnov #define IMS_PCU_CMD_OFN_GET_CONFIG 0xb4 381628329d5SDmitry Torokhov 382628329d5SDmitry Torokhov /* PCU responses */ 383628329d5SDmitry Torokhov #define IMS_PCU_RSP_STATUS 0xc0 384628329d5SDmitry Torokhov #define IMS_PCU_RSP_PCU_RESET 0 /* Originally 0xc1 */ 385628329d5SDmitry Torokhov #define IMS_PCU_RSP_RESET_REASON 0xc2 386628329d5SDmitry Torokhov #define IMS_PCU_RSP_SEND_BUTTONS 0xc3 387628329d5SDmitry Torokhov #define IMS_PCU_RSP_JUMP_TO_BTLDR 0 /* Originally 0xc4 */ 388628329d5SDmitry Torokhov #define IMS_PCU_RSP_GET_INFO 0xc5 389628329d5SDmitry Torokhov #define IMS_PCU_RSP_SET_BRIGHTNESS 0xc6 390628329d5SDmitry Torokhov #define IMS_PCU_RSP_EEPROM 0xc7 391628329d5SDmitry Torokhov #define IMS_PCU_RSP_GET_FW_VERSION 0xc8 392628329d5SDmitry Torokhov #define IMS_PCU_RSP_GET_BL_VERSION 0xc9 393628329d5SDmitry Torokhov #define IMS_PCU_RSP_SET_INFO 0xcb 394628329d5SDmitry Torokhov #define IMS_PCU_RSP_GET_BRIGHTNESS 0xcc 395628329d5SDmitry Torokhov #define IMS_PCU_RSP_CMD_INVALID 0xcd 396628329d5SDmitry Torokhov #define IMS_PCU_RSP_GET_DEVICE_ID 0xce 397628329d5SDmitry Torokhov #define IMS_PCU_RSP_SPECIAL_INFO 0xd0 398628329d5SDmitry Torokhov #define IMS_PCU_RSP_BOOTLOADER 0xd1 /* Bootloader response */ 399e5fcd269SAndrey Smirnov #define IMS_PCU_RSP_OFN_SET_CONFIG 0xd2 400e5fcd269SAndrey Smirnov #define IMS_PCU_RSP_OFN_GET_CONFIG 0xd3 401e5fcd269SAndrey Smirnov 402628329d5SDmitry Torokhov 403628329d5SDmitry Torokhov #define IMS_PCU_RSP_EVNT_BUTTONS 0xe0 /* Unsolicited, button state */ 404628329d5SDmitry Torokhov #define IMS_PCU_GAMEPAD_MASK 0x0001ff80UL /* Bits 7 through 16 */ 405628329d5SDmitry Torokhov 406628329d5SDmitry Torokhov 407628329d5SDmitry Torokhov #define IMS_PCU_MIN_PACKET_LEN 3 408628329d5SDmitry Torokhov #define IMS_PCU_DATA_OFFSET 2 409628329d5SDmitry Torokhov 410628329d5SDmitry Torokhov #define IMS_PCU_CMD_WRITE_TIMEOUT 100 /* msec */ 411628329d5SDmitry Torokhov #define IMS_PCU_CMD_RESPONSE_TIMEOUT 500 /* msec */ 412628329d5SDmitry Torokhov 413628329d5SDmitry Torokhov static void ims_pcu_report_events(struct ims_pcu *pcu) 414628329d5SDmitry Torokhov { 415628329d5SDmitry Torokhov u32 data = get_unaligned_be32(&pcu->read_buf[3]); 416628329d5SDmitry Torokhov 417628329d5SDmitry Torokhov ims_pcu_buttons_report(pcu, data & ~IMS_PCU_GAMEPAD_MASK); 418628329d5SDmitry Torokhov if (pcu->gamepad) 419628329d5SDmitry Torokhov ims_pcu_gamepad_report(pcu, data); 420628329d5SDmitry Torokhov } 421628329d5SDmitry Torokhov 422628329d5SDmitry Torokhov static void ims_pcu_handle_response(struct ims_pcu *pcu) 423628329d5SDmitry Torokhov { 424628329d5SDmitry Torokhov switch (pcu->read_buf[0]) { 425628329d5SDmitry Torokhov case IMS_PCU_RSP_EVNT_BUTTONS: 426628329d5SDmitry Torokhov if (likely(pcu->setup_complete)) 427628329d5SDmitry Torokhov ims_pcu_report_events(pcu); 428628329d5SDmitry Torokhov break; 429628329d5SDmitry Torokhov 430628329d5SDmitry Torokhov default: 431628329d5SDmitry Torokhov /* 432628329d5SDmitry Torokhov * See if we got command completion. 433628329d5SDmitry Torokhov * If both the sequence and response code match save 434628329d5SDmitry Torokhov * the data and signal completion. 435628329d5SDmitry Torokhov */ 436628329d5SDmitry Torokhov if (pcu->read_buf[0] == pcu->expected_response && 437628329d5SDmitry Torokhov pcu->read_buf[1] == pcu->ack_id - 1) { 438628329d5SDmitry Torokhov 439628329d5SDmitry Torokhov memcpy(pcu->cmd_buf, pcu->read_buf, pcu->read_pos); 440628329d5SDmitry Torokhov pcu->cmd_buf_len = pcu->read_pos; 441628329d5SDmitry Torokhov complete(&pcu->cmd_done); 442628329d5SDmitry Torokhov } 443628329d5SDmitry Torokhov break; 444628329d5SDmitry Torokhov } 445628329d5SDmitry Torokhov } 446628329d5SDmitry Torokhov 447628329d5SDmitry Torokhov static void ims_pcu_process_data(struct ims_pcu *pcu, struct urb *urb) 448628329d5SDmitry Torokhov { 449628329d5SDmitry Torokhov int i; 450628329d5SDmitry Torokhov 451628329d5SDmitry Torokhov for (i = 0; i < urb->actual_length; i++) { 452628329d5SDmitry Torokhov u8 data = pcu->urb_in_buf[i]; 453628329d5SDmitry Torokhov 454628329d5SDmitry Torokhov /* Skip everything until we get Start Xmit */ 455628329d5SDmitry Torokhov if (!pcu->have_stx && data != IMS_PCU_PROTOCOL_STX) 456628329d5SDmitry Torokhov continue; 457628329d5SDmitry Torokhov 458628329d5SDmitry Torokhov if (pcu->have_dle) { 459628329d5SDmitry Torokhov pcu->have_dle = false; 460628329d5SDmitry Torokhov pcu->read_buf[pcu->read_pos++] = data; 461628329d5SDmitry Torokhov pcu->check_sum += data; 462628329d5SDmitry Torokhov continue; 463628329d5SDmitry Torokhov } 464628329d5SDmitry Torokhov 465628329d5SDmitry Torokhov switch (data) { 466628329d5SDmitry Torokhov case IMS_PCU_PROTOCOL_STX: 467628329d5SDmitry Torokhov if (pcu->have_stx) 468628329d5SDmitry Torokhov dev_warn(pcu->dev, 469628329d5SDmitry Torokhov "Unexpected STX at byte %d, discarding old data\n", 470628329d5SDmitry Torokhov pcu->read_pos); 471628329d5SDmitry Torokhov pcu->have_stx = true; 472628329d5SDmitry Torokhov pcu->have_dle = false; 473628329d5SDmitry Torokhov pcu->read_pos = 0; 474628329d5SDmitry Torokhov pcu->check_sum = 0; 475628329d5SDmitry Torokhov break; 476628329d5SDmitry Torokhov 477628329d5SDmitry Torokhov case IMS_PCU_PROTOCOL_DLE: 478628329d5SDmitry Torokhov pcu->have_dle = true; 479628329d5SDmitry Torokhov break; 480628329d5SDmitry Torokhov 481628329d5SDmitry Torokhov case IMS_PCU_PROTOCOL_ETX: 482628329d5SDmitry Torokhov if (pcu->read_pos < IMS_PCU_MIN_PACKET_LEN) { 483628329d5SDmitry Torokhov dev_warn(pcu->dev, 484628329d5SDmitry Torokhov "Short packet received (%d bytes), ignoring\n", 485628329d5SDmitry Torokhov pcu->read_pos); 486628329d5SDmitry Torokhov } else if (pcu->check_sum != 0) { 487628329d5SDmitry Torokhov dev_warn(pcu->dev, 488628329d5SDmitry Torokhov "Invalid checksum in packet (%d bytes), ignoring\n", 489628329d5SDmitry Torokhov pcu->read_pos); 490628329d5SDmitry Torokhov } else { 491628329d5SDmitry Torokhov ims_pcu_handle_response(pcu); 492628329d5SDmitry Torokhov } 493628329d5SDmitry Torokhov 494628329d5SDmitry Torokhov pcu->have_stx = false; 495628329d5SDmitry Torokhov pcu->have_dle = false; 496628329d5SDmitry Torokhov pcu->read_pos = 0; 497628329d5SDmitry Torokhov break; 498628329d5SDmitry Torokhov 499628329d5SDmitry Torokhov default: 500628329d5SDmitry Torokhov pcu->read_buf[pcu->read_pos++] = data; 501628329d5SDmitry Torokhov pcu->check_sum += data; 502628329d5SDmitry Torokhov break; 503628329d5SDmitry Torokhov } 504628329d5SDmitry Torokhov } 505628329d5SDmitry Torokhov } 506628329d5SDmitry Torokhov 507628329d5SDmitry Torokhov static bool ims_pcu_byte_needs_escape(u8 byte) 508628329d5SDmitry Torokhov { 509628329d5SDmitry Torokhov return byte == IMS_PCU_PROTOCOL_STX || 510628329d5SDmitry Torokhov byte == IMS_PCU_PROTOCOL_ETX || 511628329d5SDmitry Torokhov byte == IMS_PCU_PROTOCOL_DLE; 512628329d5SDmitry Torokhov } 513628329d5SDmitry Torokhov 514628329d5SDmitry Torokhov static int ims_pcu_send_cmd_chunk(struct ims_pcu *pcu, 515628329d5SDmitry Torokhov u8 command, int chunk, int len) 516628329d5SDmitry Torokhov { 517628329d5SDmitry Torokhov int error; 518628329d5SDmitry Torokhov 519628329d5SDmitry Torokhov error = usb_bulk_msg(pcu->udev, 520628329d5SDmitry Torokhov usb_sndbulkpipe(pcu->udev, 521628329d5SDmitry Torokhov pcu->ep_out->bEndpointAddress), 522628329d5SDmitry Torokhov pcu->urb_out_buf, len, 523628329d5SDmitry Torokhov NULL, IMS_PCU_CMD_WRITE_TIMEOUT); 524628329d5SDmitry Torokhov if (error < 0) { 525628329d5SDmitry Torokhov dev_dbg(pcu->dev, 526628329d5SDmitry Torokhov "Sending 0x%02x command failed at chunk %d: %d\n", 527628329d5SDmitry Torokhov command, chunk, error); 528628329d5SDmitry Torokhov return error; 529628329d5SDmitry Torokhov } 530628329d5SDmitry Torokhov 531628329d5SDmitry Torokhov return 0; 532628329d5SDmitry Torokhov } 533628329d5SDmitry Torokhov 534628329d5SDmitry Torokhov static int ims_pcu_send_command(struct ims_pcu *pcu, 535628329d5SDmitry Torokhov u8 command, const u8 *data, int len) 536628329d5SDmitry Torokhov { 537628329d5SDmitry Torokhov int count = 0; 538628329d5SDmitry Torokhov int chunk = 0; 539628329d5SDmitry Torokhov int delta; 540628329d5SDmitry Torokhov int i; 541628329d5SDmitry Torokhov int error; 542628329d5SDmitry Torokhov u8 csum = 0; 543628329d5SDmitry Torokhov u8 ack_id; 544628329d5SDmitry Torokhov 545628329d5SDmitry Torokhov pcu->urb_out_buf[count++] = IMS_PCU_PROTOCOL_STX; 546628329d5SDmitry Torokhov 547628329d5SDmitry Torokhov /* We know the command need not be escaped */ 548628329d5SDmitry Torokhov pcu->urb_out_buf[count++] = command; 549628329d5SDmitry Torokhov csum += command; 550628329d5SDmitry Torokhov 551628329d5SDmitry Torokhov ack_id = pcu->ack_id++; 552628329d5SDmitry Torokhov if (ack_id == 0xff) 553628329d5SDmitry Torokhov ack_id = pcu->ack_id++; 554628329d5SDmitry Torokhov 555628329d5SDmitry Torokhov if (ims_pcu_byte_needs_escape(ack_id)) 556628329d5SDmitry Torokhov pcu->urb_out_buf[count++] = IMS_PCU_PROTOCOL_DLE; 557628329d5SDmitry Torokhov 558628329d5SDmitry Torokhov pcu->urb_out_buf[count++] = ack_id; 559628329d5SDmitry Torokhov csum += ack_id; 560628329d5SDmitry Torokhov 561628329d5SDmitry Torokhov for (i = 0; i < len; i++) { 562628329d5SDmitry Torokhov 563628329d5SDmitry Torokhov delta = ims_pcu_byte_needs_escape(data[i]) ? 2 : 1; 564628329d5SDmitry Torokhov if (count + delta >= pcu->max_out_size) { 565628329d5SDmitry Torokhov error = ims_pcu_send_cmd_chunk(pcu, command, 566628329d5SDmitry Torokhov ++chunk, count); 567628329d5SDmitry Torokhov if (error) 568628329d5SDmitry Torokhov return error; 569628329d5SDmitry Torokhov 570628329d5SDmitry Torokhov count = 0; 571628329d5SDmitry Torokhov } 572628329d5SDmitry Torokhov 573628329d5SDmitry Torokhov if (delta == 2) 574628329d5SDmitry Torokhov pcu->urb_out_buf[count++] = IMS_PCU_PROTOCOL_DLE; 575628329d5SDmitry Torokhov 576628329d5SDmitry Torokhov pcu->urb_out_buf[count++] = data[i]; 577628329d5SDmitry Torokhov csum += data[i]; 578628329d5SDmitry Torokhov } 579628329d5SDmitry Torokhov 580628329d5SDmitry Torokhov csum = 1 + ~csum; 581628329d5SDmitry Torokhov 582628329d5SDmitry Torokhov delta = ims_pcu_byte_needs_escape(csum) ? 3 : 2; 583628329d5SDmitry Torokhov if (count + delta >= pcu->max_out_size) { 584628329d5SDmitry Torokhov error = ims_pcu_send_cmd_chunk(pcu, command, ++chunk, count); 585628329d5SDmitry Torokhov if (error) 586628329d5SDmitry Torokhov return error; 587628329d5SDmitry Torokhov 588628329d5SDmitry Torokhov count = 0; 589628329d5SDmitry Torokhov } 590628329d5SDmitry Torokhov 591628329d5SDmitry Torokhov if (delta == 3) 592628329d5SDmitry Torokhov pcu->urb_out_buf[count++] = IMS_PCU_PROTOCOL_DLE; 593628329d5SDmitry Torokhov 594628329d5SDmitry Torokhov pcu->urb_out_buf[count++] = csum; 595628329d5SDmitry Torokhov pcu->urb_out_buf[count++] = IMS_PCU_PROTOCOL_ETX; 596628329d5SDmitry Torokhov 597628329d5SDmitry Torokhov return ims_pcu_send_cmd_chunk(pcu, command, ++chunk, count); 598628329d5SDmitry Torokhov } 599628329d5SDmitry Torokhov 600628329d5SDmitry Torokhov static int __ims_pcu_execute_command(struct ims_pcu *pcu, 601628329d5SDmitry Torokhov u8 command, const void *data, size_t len, 602628329d5SDmitry Torokhov u8 expected_response, int response_time) 603628329d5SDmitry Torokhov { 604628329d5SDmitry Torokhov int error; 605628329d5SDmitry Torokhov 606628329d5SDmitry Torokhov pcu->expected_response = expected_response; 607628329d5SDmitry Torokhov init_completion(&pcu->cmd_done); 608628329d5SDmitry Torokhov 609628329d5SDmitry Torokhov error = ims_pcu_send_command(pcu, command, data, len); 610628329d5SDmitry Torokhov if (error) 611628329d5SDmitry Torokhov return error; 612628329d5SDmitry Torokhov 613628329d5SDmitry Torokhov if (expected_response && 614628329d5SDmitry Torokhov !wait_for_completion_timeout(&pcu->cmd_done, 615628329d5SDmitry Torokhov msecs_to_jiffies(response_time))) { 616628329d5SDmitry Torokhov dev_dbg(pcu->dev, "Command 0x%02x timed out\n", command); 617628329d5SDmitry Torokhov return -ETIMEDOUT; 618628329d5SDmitry Torokhov } 619628329d5SDmitry Torokhov 620628329d5SDmitry Torokhov return 0; 621628329d5SDmitry Torokhov } 622628329d5SDmitry Torokhov 623628329d5SDmitry Torokhov #define ims_pcu_execute_command(pcu, code, data, len) \ 624628329d5SDmitry Torokhov __ims_pcu_execute_command(pcu, \ 625628329d5SDmitry Torokhov IMS_PCU_CMD_##code, data, len, \ 626628329d5SDmitry Torokhov IMS_PCU_RSP_##code, \ 627628329d5SDmitry Torokhov IMS_PCU_CMD_RESPONSE_TIMEOUT) 628628329d5SDmitry Torokhov 629628329d5SDmitry Torokhov #define ims_pcu_execute_query(pcu, code) \ 630628329d5SDmitry Torokhov ims_pcu_execute_command(pcu, code, NULL, 0) 631628329d5SDmitry Torokhov 632628329d5SDmitry Torokhov /* Bootloader commands */ 633628329d5SDmitry Torokhov #define IMS_PCU_BL_CMD_QUERY_DEVICE 0xa1 634628329d5SDmitry Torokhov #define IMS_PCU_BL_CMD_UNLOCK_CONFIG 0xa2 635628329d5SDmitry Torokhov #define IMS_PCU_BL_CMD_ERASE_APP 0xa3 636628329d5SDmitry Torokhov #define IMS_PCU_BL_CMD_PROGRAM_DEVICE 0xa4 637628329d5SDmitry Torokhov #define IMS_PCU_BL_CMD_PROGRAM_COMPLETE 0xa5 638628329d5SDmitry Torokhov #define IMS_PCU_BL_CMD_READ_APP 0xa6 639628329d5SDmitry Torokhov #define IMS_PCU_BL_CMD_RESET_DEVICE 0xa7 640628329d5SDmitry Torokhov #define IMS_PCU_BL_CMD_LAUNCH_APP 0xa8 641628329d5SDmitry Torokhov 642628329d5SDmitry Torokhov /* Bootloader commands */ 643628329d5SDmitry Torokhov #define IMS_PCU_BL_RSP_QUERY_DEVICE 0xc1 644628329d5SDmitry Torokhov #define IMS_PCU_BL_RSP_UNLOCK_CONFIG 0xc2 645628329d5SDmitry Torokhov #define IMS_PCU_BL_RSP_ERASE_APP 0xc3 646628329d5SDmitry Torokhov #define IMS_PCU_BL_RSP_PROGRAM_DEVICE 0xc4 647628329d5SDmitry Torokhov #define IMS_PCU_BL_RSP_PROGRAM_COMPLETE 0xc5 648628329d5SDmitry Torokhov #define IMS_PCU_BL_RSP_READ_APP 0xc6 649628329d5SDmitry Torokhov #define IMS_PCU_BL_RSP_RESET_DEVICE 0 /* originally 0xa7 */ 650628329d5SDmitry Torokhov #define IMS_PCU_BL_RSP_LAUNCH_APP 0 /* originally 0xa8 */ 651628329d5SDmitry Torokhov 652628329d5SDmitry Torokhov #define IMS_PCU_BL_DATA_OFFSET 3 653628329d5SDmitry Torokhov 654628329d5SDmitry Torokhov static int __ims_pcu_execute_bl_command(struct ims_pcu *pcu, 655628329d5SDmitry Torokhov u8 command, const void *data, size_t len, 656628329d5SDmitry Torokhov u8 expected_response, int response_time) 657628329d5SDmitry Torokhov { 658628329d5SDmitry Torokhov int error; 659628329d5SDmitry Torokhov 660628329d5SDmitry Torokhov pcu->cmd_buf[0] = command; 661628329d5SDmitry Torokhov if (data) 662628329d5SDmitry Torokhov memcpy(&pcu->cmd_buf[1], data, len); 663628329d5SDmitry Torokhov 664628329d5SDmitry Torokhov error = __ims_pcu_execute_command(pcu, 665628329d5SDmitry Torokhov IMS_PCU_CMD_BOOTLOADER, pcu->cmd_buf, len + 1, 666628329d5SDmitry Torokhov expected_response ? IMS_PCU_RSP_BOOTLOADER : 0, 667628329d5SDmitry Torokhov response_time); 668628329d5SDmitry Torokhov if (error) { 669628329d5SDmitry Torokhov dev_err(pcu->dev, 670628329d5SDmitry Torokhov "Failure when sending 0x%02x command to bootloader, error: %d\n", 671628329d5SDmitry Torokhov pcu->cmd_buf[0], error); 672628329d5SDmitry Torokhov return error; 673628329d5SDmitry Torokhov } 674628329d5SDmitry Torokhov 675628329d5SDmitry Torokhov if (expected_response && pcu->cmd_buf[2] != expected_response) { 676628329d5SDmitry Torokhov dev_err(pcu->dev, 677628329d5SDmitry Torokhov "Unexpected response from bootloader: 0x%02x, wanted 0x%02x\n", 678628329d5SDmitry Torokhov pcu->cmd_buf[2], expected_response); 679628329d5SDmitry Torokhov return -EINVAL; 680628329d5SDmitry Torokhov } 681628329d5SDmitry Torokhov 682628329d5SDmitry Torokhov return 0; 683628329d5SDmitry Torokhov } 684628329d5SDmitry Torokhov 685628329d5SDmitry Torokhov #define ims_pcu_execute_bl_command(pcu, code, data, len, timeout) \ 686628329d5SDmitry Torokhov __ims_pcu_execute_bl_command(pcu, \ 687628329d5SDmitry Torokhov IMS_PCU_BL_CMD_##code, data, len, \ 688628329d5SDmitry Torokhov IMS_PCU_BL_RSP_##code, timeout) \ 689628329d5SDmitry Torokhov 690628329d5SDmitry Torokhov #define IMS_PCU_INFO_PART_OFFSET 2 691628329d5SDmitry Torokhov #define IMS_PCU_INFO_DOM_OFFSET 17 692628329d5SDmitry Torokhov #define IMS_PCU_INFO_SERIAL_OFFSET 25 693628329d5SDmitry Torokhov 694628329d5SDmitry Torokhov #define IMS_PCU_SET_INFO_SIZE 31 695628329d5SDmitry Torokhov 696628329d5SDmitry Torokhov static int ims_pcu_get_info(struct ims_pcu *pcu) 697628329d5SDmitry Torokhov { 698628329d5SDmitry Torokhov int error; 699628329d5SDmitry Torokhov 700628329d5SDmitry Torokhov error = ims_pcu_execute_query(pcu, GET_INFO); 701628329d5SDmitry Torokhov if (error) { 702628329d5SDmitry Torokhov dev_err(pcu->dev, 703628329d5SDmitry Torokhov "GET_INFO command failed, error: %d\n", error); 704628329d5SDmitry Torokhov return error; 705628329d5SDmitry Torokhov } 706628329d5SDmitry Torokhov 707628329d5SDmitry Torokhov memcpy(pcu->part_number, 708628329d5SDmitry Torokhov &pcu->cmd_buf[IMS_PCU_INFO_PART_OFFSET], 709628329d5SDmitry Torokhov sizeof(pcu->part_number)); 710628329d5SDmitry Torokhov memcpy(pcu->date_of_manufacturing, 711628329d5SDmitry Torokhov &pcu->cmd_buf[IMS_PCU_INFO_DOM_OFFSET], 712628329d5SDmitry Torokhov sizeof(pcu->date_of_manufacturing)); 713628329d5SDmitry Torokhov memcpy(pcu->serial_number, 714628329d5SDmitry Torokhov &pcu->cmd_buf[IMS_PCU_INFO_SERIAL_OFFSET], 715628329d5SDmitry Torokhov sizeof(pcu->serial_number)); 716628329d5SDmitry Torokhov 717628329d5SDmitry Torokhov return 0; 718628329d5SDmitry Torokhov } 719628329d5SDmitry Torokhov 720628329d5SDmitry Torokhov static int ims_pcu_set_info(struct ims_pcu *pcu) 721628329d5SDmitry Torokhov { 722628329d5SDmitry Torokhov int error; 723628329d5SDmitry Torokhov 724628329d5SDmitry Torokhov memcpy(&pcu->cmd_buf[IMS_PCU_INFO_PART_OFFSET], 725628329d5SDmitry Torokhov pcu->part_number, sizeof(pcu->part_number)); 726628329d5SDmitry Torokhov memcpy(&pcu->cmd_buf[IMS_PCU_INFO_DOM_OFFSET], 727628329d5SDmitry Torokhov pcu->date_of_manufacturing, sizeof(pcu->date_of_manufacturing)); 728628329d5SDmitry Torokhov memcpy(&pcu->cmd_buf[IMS_PCU_INFO_SERIAL_OFFSET], 729628329d5SDmitry Torokhov pcu->serial_number, sizeof(pcu->serial_number)); 730628329d5SDmitry Torokhov 731628329d5SDmitry Torokhov error = ims_pcu_execute_command(pcu, SET_INFO, 732628329d5SDmitry Torokhov &pcu->cmd_buf[IMS_PCU_DATA_OFFSET], 733628329d5SDmitry Torokhov IMS_PCU_SET_INFO_SIZE); 734628329d5SDmitry Torokhov if (error) { 735628329d5SDmitry Torokhov dev_err(pcu->dev, 736628329d5SDmitry Torokhov "Failed to update device information, error: %d\n", 737628329d5SDmitry Torokhov error); 738628329d5SDmitry Torokhov return error; 739628329d5SDmitry Torokhov } 740628329d5SDmitry Torokhov 741628329d5SDmitry Torokhov return 0; 742628329d5SDmitry Torokhov } 743628329d5SDmitry Torokhov 744628329d5SDmitry Torokhov static int ims_pcu_switch_to_bootloader(struct ims_pcu *pcu) 745628329d5SDmitry Torokhov { 746628329d5SDmitry Torokhov int error; 747628329d5SDmitry Torokhov 748628329d5SDmitry Torokhov /* Execute jump to the bootoloader */ 749628329d5SDmitry Torokhov error = ims_pcu_execute_command(pcu, JUMP_TO_BTLDR, NULL, 0); 750628329d5SDmitry Torokhov if (error) { 751628329d5SDmitry Torokhov dev_err(pcu->dev, 752628329d5SDmitry Torokhov "Failure when sending JUMP TO BOOLTLOADER command, error: %d\n", 753628329d5SDmitry Torokhov error); 754628329d5SDmitry Torokhov return error; 755628329d5SDmitry Torokhov } 756628329d5SDmitry Torokhov 757628329d5SDmitry Torokhov return 0; 758628329d5SDmitry Torokhov } 759628329d5SDmitry Torokhov 760628329d5SDmitry Torokhov /********************************************************************* 761628329d5SDmitry Torokhov * Firmware Update handling * 762628329d5SDmitry Torokhov *********************************************************************/ 763628329d5SDmitry Torokhov 764628329d5SDmitry Torokhov #define IMS_PCU_FIRMWARE_NAME "imspcu.fw" 765628329d5SDmitry Torokhov 766628329d5SDmitry Torokhov struct ims_pcu_flash_fmt { 767628329d5SDmitry Torokhov __le32 addr; 768628329d5SDmitry Torokhov u8 len; 769628329d5SDmitry Torokhov u8 data[]; 770628329d5SDmitry Torokhov }; 771628329d5SDmitry Torokhov 772628329d5SDmitry Torokhov static unsigned int ims_pcu_count_fw_records(const struct firmware *fw) 773628329d5SDmitry Torokhov { 774628329d5SDmitry Torokhov const struct ihex_binrec *rec = (const struct ihex_binrec *)fw->data; 775628329d5SDmitry Torokhov unsigned int count = 0; 776628329d5SDmitry Torokhov 777628329d5SDmitry Torokhov while (rec) { 778628329d5SDmitry Torokhov count++; 779628329d5SDmitry Torokhov rec = ihex_next_binrec(rec); 780628329d5SDmitry Torokhov } 781628329d5SDmitry Torokhov 782628329d5SDmitry Torokhov return count; 783628329d5SDmitry Torokhov } 784628329d5SDmitry Torokhov 785628329d5SDmitry Torokhov static int ims_pcu_verify_block(struct ims_pcu *pcu, 786628329d5SDmitry Torokhov u32 addr, u8 len, const u8 *data) 787628329d5SDmitry Torokhov { 788628329d5SDmitry Torokhov struct ims_pcu_flash_fmt *fragment; 789628329d5SDmitry Torokhov int error; 790628329d5SDmitry Torokhov 791628329d5SDmitry Torokhov fragment = (void *)&pcu->cmd_buf[1]; 792628329d5SDmitry Torokhov put_unaligned_le32(addr, &fragment->addr); 793628329d5SDmitry Torokhov fragment->len = len; 794628329d5SDmitry Torokhov 795628329d5SDmitry Torokhov error = ims_pcu_execute_bl_command(pcu, READ_APP, NULL, 5, 796628329d5SDmitry Torokhov IMS_PCU_CMD_RESPONSE_TIMEOUT); 797628329d5SDmitry Torokhov if (error) { 798628329d5SDmitry Torokhov dev_err(pcu->dev, 799628329d5SDmitry Torokhov "Failed to retrieve block at 0x%08x, len %d, error: %d\n", 800628329d5SDmitry Torokhov addr, len, error); 801628329d5SDmitry Torokhov return error; 802628329d5SDmitry Torokhov } 803628329d5SDmitry Torokhov 804628329d5SDmitry Torokhov fragment = (void *)&pcu->cmd_buf[IMS_PCU_BL_DATA_OFFSET]; 805628329d5SDmitry Torokhov if (get_unaligned_le32(&fragment->addr) != addr || 806628329d5SDmitry Torokhov fragment->len != len) { 807628329d5SDmitry Torokhov dev_err(pcu->dev, 808628329d5SDmitry Torokhov "Wrong block when retrieving 0x%08x (0x%08x), len %d (%d)\n", 809628329d5SDmitry Torokhov addr, get_unaligned_le32(&fragment->addr), 810628329d5SDmitry Torokhov len, fragment->len); 811628329d5SDmitry Torokhov return -EINVAL; 812628329d5SDmitry Torokhov } 813628329d5SDmitry Torokhov 814628329d5SDmitry Torokhov if (memcmp(fragment->data, data, len)) { 815628329d5SDmitry Torokhov dev_err(pcu->dev, 816628329d5SDmitry Torokhov "Mismatch in block at 0x%08x, len %d\n", 817628329d5SDmitry Torokhov addr, len); 818628329d5SDmitry Torokhov return -EINVAL; 819628329d5SDmitry Torokhov } 820628329d5SDmitry Torokhov 821628329d5SDmitry Torokhov return 0; 822628329d5SDmitry Torokhov } 823628329d5SDmitry Torokhov 824628329d5SDmitry Torokhov static int ims_pcu_flash_firmware(struct ims_pcu *pcu, 825628329d5SDmitry Torokhov const struct firmware *fw, 826628329d5SDmitry Torokhov unsigned int n_fw_records) 827628329d5SDmitry Torokhov { 828628329d5SDmitry Torokhov const struct ihex_binrec *rec = (const struct ihex_binrec *)fw->data; 829628329d5SDmitry Torokhov struct ims_pcu_flash_fmt *fragment; 830628329d5SDmitry Torokhov unsigned int count = 0; 831628329d5SDmitry Torokhov u32 addr; 832628329d5SDmitry Torokhov u8 len; 833628329d5SDmitry Torokhov int error; 834628329d5SDmitry Torokhov 835628329d5SDmitry Torokhov error = ims_pcu_execute_bl_command(pcu, ERASE_APP, NULL, 0, 2000); 836628329d5SDmitry Torokhov if (error) { 837628329d5SDmitry Torokhov dev_err(pcu->dev, 838628329d5SDmitry Torokhov "Failed to erase application image, error: %d\n", 839628329d5SDmitry Torokhov error); 840628329d5SDmitry Torokhov return error; 841628329d5SDmitry Torokhov } 842628329d5SDmitry Torokhov 843628329d5SDmitry Torokhov while (rec) { 844628329d5SDmitry Torokhov /* 845628329d5SDmitry Torokhov * The firmware format is messed up for some reason. 846628329d5SDmitry Torokhov * The address twice that of what is needed for some 847628329d5SDmitry Torokhov * reason and we end up overwriting half of the data 848628329d5SDmitry Torokhov * with the next record. 849628329d5SDmitry Torokhov */ 850628329d5SDmitry Torokhov addr = be32_to_cpu(rec->addr) / 2; 851628329d5SDmitry Torokhov len = be16_to_cpu(rec->len); 852628329d5SDmitry Torokhov 853628329d5SDmitry Torokhov fragment = (void *)&pcu->cmd_buf[1]; 854628329d5SDmitry Torokhov put_unaligned_le32(addr, &fragment->addr); 855628329d5SDmitry Torokhov fragment->len = len; 856628329d5SDmitry Torokhov memcpy(fragment->data, rec->data, len); 857628329d5SDmitry Torokhov 858628329d5SDmitry Torokhov error = ims_pcu_execute_bl_command(pcu, PROGRAM_DEVICE, 859628329d5SDmitry Torokhov NULL, len + 5, 860628329d5SDmitry Torokhov IMS_PCU_CMD_RESPONSE_TIMEOUT); 861628329d5SDmitry Torokhov if (error) { 862628329d5SDmitry Torokhov dev_err(pcu->dev, 863628329d5SDmitry Torokhov "Failed to write block at 0x%08x, len %d, error: %d\n", 864628329d5SDmitry Torokhov addr, len, error); 865628329d5SDmitry Torokhov return error; 866628329d5SDmitry Torokhov } 867628329d5SDmitry Torokhov 868628329d5SDmitry Torokhov if (addr >= pcu->fw_start_addr && addr < pcu->fw_end_addr) { 869628329d5SDmitry Torokhov error = ims_pcu_verify_block(pcu, addr, len, rec->data); 870628329d5SDmitry Torokhov if (error) 871628329d5SDmitry Torokhov return error; 872628329d5SDmitry Torokhov } 873628329d5SDmitry Torokhov 874628329d5SDmitry Torokhov count++; 875628329d5SDmitry Torokhov pcu->update_firmware_status = (count * 100) / n_fw_records; 876628329d5SDmitry Torokhov 877628329d5SDmitry Torokhov rec = ihex_next_binrec(rec); 878628329d5SDmitry Torokhov } 879628329d5SDmitry Torokhov 880628329d5SDmitry Torokhov error = ims_pcu_execute_bl_command(pcu, PROGRAM_COMPLETE, 881628329d5SDmitry Torokhov NULL, 0, 2000); 882628329d5SDmitry Torokhov if (error) 883628329d5SDmitry Torokhov dev_err(pcu->dev, 884628329d5SDmitry Torokhov "Failed to send PROGRAM_COMPLETE, error: %d\n", 885628329d5SDmitry Torokhov error); 886628329d5SDmitry Torokhov 887628329d5SDmitry Torokhov return 0; 888628329d5SDmitry Torokhov } 889628329d5SDmitry Torokhov 890628329d5SDmitry Torokhov static int ims_pcu_handle_firmware_update(struct ims_pcu *pcu, 891628329d5SDmitry Torokhov const struct firmware *fw) 892628329d5SDmitry Torokhov { 893628329d5SDmitry Torokhov unsigned int n_fw_records; 894628329d5SDmitry Torokhov int retval; 895628329d5SDmitry Torokhov 896628329d5SDmitry Torokhov dev_info(pcu->dev, "Updating firmware %s, size: %zu\n", 897628329d5SDmitry Torokhov IMS_PCU_FIRMWARE_NAME, fw->size); 898628329d5SDmitry Torokhov 899628329d5SDmitry Torokhov n_fw_records = ims_pcu_count_fw_records(fw); 900628329d5SDmitry Torokhov 901628329d5SDmitry Torokhov retval = ims_pcu_flash_firmware(pcu, fw, n_fw_records); 902628329d5SDmitry Torokhov if (retval) 903628329d5SDmitry Torokhov goto out; 904628329d5SDmitry Torokhov 905628329d5SDmitry Torokhov retval = ims_pcu_execute_bl_command(pcu, LAUNCH_APP, NULL, 0, 0); 906628329d5SDmitry Torokhov if (retval) 907628329d5SDmitry Torokhov dev_err(pcu->dev, 908628329d5SDmitry Torokhov "Failed to start application image, error: %d\n", 909628329d5SDmitry Torokhov retval); 910628329d5SDmitry Torokhov 911628329d5SDmitry Torokhov out: 912628329d5SDmitry Torokhov pcu->update_firmware_status = retval; 913628329d5SDmitry Torokhov sysfs_notify(&pcu->dev->kobj, NULL, "update_firmware_status"); 914628329d5SDmitry Torokhov return retval; 915628329d5SDmitry Torokhov } 916628329d5SDmitry Torokhov 917628329d5SDmitry Torokhov static void ims_pcu_process_async_firmware(const struct firmware *fw, 918628329d5SDmitry Torokhov void *context) 919628329d5SDmitry Torokhov { 920628329d5SDmitry Torokhov struct ims_pcu *pcu = context; 921628329d5SDmitry Torokhov int error; 922628329d5SDmitry Torokhov 923628329d5SDmitry Torokhov if (!fw) { 924628329d5SDmitry Torokhov dev_err(pcu->dev, "Failed to get firmware %s\n", 925628329d5SDmitry Torokhov IMS_PCU_FIRMWARE_NAME); 926628329d5SDmitry Torokhov goto out; 927628329d5SDmitry Torokhov } 928628329d5SDmitry Torokhov 929628329d5SDmitry Torokhov error = ihex_validate_fw(fw); 930628329d5SDmitry Torokhov if (error) { 931628329d5SDmitry Torokhov dev_err(pcu->dev, "Firmware %s is invalid\n", 932628329d5SDmitry Torokhov IMS_PCU_FIRMWARE_NAME); 933628329d5SDmitry Torokhov goto out; 934628329d5SDmitry Torokhov } 935628329d5SDmitry Torokhov 936628329d5SDmitry Torokhov mutex_lock(&pcu->cmd_mutex); 937628329d5SDmitry Torokhov ims_pcu_handle_firmware_update(pcu, fw); 938628329d5SDmitry Torokhov mutex_unlock(&pcu->cmd_mutex); 939628329d5SDmitry Torokhov 940628329d5SDmitry Torokhov release_firmware(fw); 941628329d5SDmitry Torokhov 942628329d5SDmitry Torokhov out: 943628329d5SDmitry Torokhov complete(&pcu->async_firmware_done); 944628329d5SDmitry Torokhov } 945628329d5SDmitry Torokhov 946628329d5SDmitry Torokhov /********************************************************************* 947628329d5SDmitry Torokhov * Backlight LED device support * 948628329d5SDmitry Torokhov *********************************************************************/ 949628329d5SDmitry Torokhov 950628329d5SDmitry Torokhov #define IMS_PCU_MAX_BRIGHTNESS 31998 951628329d5SDmitry Torokhov 952628329d5SDmitry Torokhov static void ims_pcu_backlight_work(struct work_struct *work) 953628329d5SDmitry Torokhov { 954628329d5SDmitry Torokhov struct ims_pcu_backlight *backlight = 955628329d5SDmitry Torokhov container_of(work, struct ims_pcu_backlight, work); 956628329d5SDmitry Torokhov struct ims_pcu *pcu = 957628329d5SDmitry Torokhov container_of(backlight, struct ims_pcu, backlight); 958628329d5SDmitry Torokhov int desired_brightness = backlight->desired_brightness; 959628329d5SDmitry Torokhov __le16 br_val = cpu_to_le16(desired_brightness); 960628329d5SDmitry Torokhov int error; 961628329d5SDmitry Torokhov 962628329d5SDmitry Torokhov mutex_lock(&pcu->cmd_mutex); 963628329d5SDmitry Torokhov 964628329d5SDmitry Torokhov error = ims_pcu_execute_command(pcu, SET_BRIGHTNESS, 965628329d5SDmitry Torokhov &br_val, sizeof(br_val)); 966628329d5SDmitry Torokhov if (error && error != -ENODEV) 967628329d5SDmitry Torokhov dev_warn(pcu->dev, 968628329d5SDmitry Torokhov "Failed to set desired brightness %u, error: %d\n", 969628329d5SDmitry Torokhov desired_brightness, error); 970628329d5SDmitry Torokhov 971628329d5SDmitry Torokhov mutex_unlock(&pcu->cmd_mutex); 972628329d5SDmitry Torokhov } 973628329d5SDmitry Torokhov 974628329d5SDmitry Torokhov static void ims_pcu_backlight_set_brightness(struct led_classdev *cdev, 975628329d5SDmitry Torokhov enum led_brightness value) 976628329d5SDmitry Torokhov { 977628329d5SDmitry Torokhov struct ims_pcu_backlight *backlight = 978628329d5SDmitry Torokhov container_of(cdev, struct ims_pcu_backlight, cdev); 979628329d5SDmitry Torokhov 980628329d5SDmitry Torokhov backlight->desired_brightness = value; 981628329d5SDmitry Torokhov schedule_work(&backlight->work); 982628329d5SDmitry Torokhov } 983628329d5SDmitry Torokhov 984628329d5SDmitry Torokhov static enum led_brightness 985628329d5SDmitry Torokhov ims_pcu_backlight_get_brightness(struct led_classdev *cdev) 986628329d5SDmitry Torokhov { 987628329d5SDmitry Torokhov struct ims_pcu_backlight *backlight = 988628329d5SDmitry Torokhov container_of(cdev, struct ims_pcu_backlight, cdev); 989628329d5SDmitry Torokhov struct ims_pcu *pcu = 990628329d5SDmitry Torokhov container_of(backlight, struct ims_pcu, backlight); 991628329d5SDmitry Torokhov int brightness; 992628329d5SDmitry Torokhov int error; 993628329d5SDmitry Torokhov 994628329d5SDmitry Torokhov mutex_lock(&pcu->cmd_mutex); 995628329d5SDmitry Torokhov 996628329d5SDmitry Torokhov error = ims_pcu_execute_query(pcu, GET_BRIGHTNESS); 997628329d5SDmitry Torokhov if (error) { 998628329d5SDmitry Torokhov dev_warn(pcu->dev, 999628329d5SDmitry Torokhov "Failed to get current brightness, error: %d\n", 1000628329d5SDmitry Torokhov error); 1001628329d5SDmitry Torokhov /* Assume the LED is OFF */ 1002628329d5SDmitry Torokhov brightness = LED_OFF; 1003628329d5SDmitry Torokhov } else { 1004628329d5SDmitry Torokhov brightness = 1005628329d5SDmitry Torokhov get_unaligned_le16(&pcu->cmd_buf[IMS_PCU_DATA_OFFSET]); 1006628329d5SDmitry Torokhov } 1007628329d5SDmitry Torokhov 1008628329d5SDmitry Torokhov mutex_unlock(&pcu->cmd_mutex); 1009628329d5SDmitry Torokhov 1010628329d5SDmitry Torokhov return brightness; 1011628329d5SDmitry Torokhov } 1012628329d5SDmitry Torokhov 1013628329d5SDmitry Torokhov static int ims_pcu_setup_backlight(struct ims_pcu *pcu) 1014628329d5SDmitry Torokhov { 1015628329d5SDmitry Torokhov struct ims_pcu_backlight *backlight = &pcu->backlight; 1016628329d5SDmitry Torokhov int error; 1017628329d5SDmitry Torokhov 1018628329d5SDmitry Torokhov INIT_WORK(&backlight->work, ims_pcu_backlight_work); 1019628329d5SDmitry Torokhov snprintf(backlight->name, sizeof(backlight->name), 1020628329d5SDmitry Torokhov "pcu%d::kbd_backlight", pcu->device_no); 1021628329d5SDmitry Torokhov 1022628329d5SDmitry Torokhov backlight->cdev.name = backlight->name; 1023628329d5SDmitry Torokhov backlight->cdev.max_brightness = IMS_PCU_MAX_BRIGHTNESS; 1024628329d5SDmitry Torokhov backlight->cdev.brightness_get = ims_pcu_backlight_get_brightness; 1025628329d5SDmitry Torokhov backlight->cdev.brightness_set = ims_pcu_backlight_set_brightness; 1026628329d5SDmitry Torokhov 1027628329d5SDmitry Torokhov error = led_classdev_register(pcu->dev, &backlight->cdev); 1028628329d5SDmitry Torokhov if (error) { 1029628329d5SDmitry Torokhov dev_err(pcu->dev, 1030628329d5SDmitry Torokhov "Failed to register backlight LED device, error: %d\n", 1031628329d5SDmitry Torokhov error); 1032628329d5SDmitry Torokhov return error; 1033628329d5SDmitry Torokhov } 1034628329d5SDmitry Torokhov 1035628329d5SDmitry Torokhov return 0; 1036628329d5SDmitry Torokhov } 1037628329d5SDmitry Torokhov 1038628329d5SDmitry Torokhov static void ims_pcu_destroy_backlight(struct ims_pcu *pcu) 1039628329d5SDmitry Torokhov { 1040628329d5SDmitry Torokhov struct ims_pcu_backlight *backlight = &pcu->backlight; 1041628329d5SDmitry Torokhov 1042628329d5SDmitry Torokhov led_classdev_unregister(&backlight->cdev); 1043628329d5SDmitry Torokhov cancel_work_sync(&backlight->work); 1044628329d5SDmitry Torokhov } 1045628329d5SDmitry Torokhov 1046628329d5SDmitry Torokhov 1047628329d5SDmitry Torokhov /********************************************************************* 1048628329d5SDmitry Torokhov * Sysfs attributes handling * 1049628329d5SDmitry Torokhov *********************************************************************/ 1050628329d5SDmitry Torokhov 1051628329d5SDmitry Torokhov struct ims_pcu_attribute { 1052628329d5SDmitry Torokhov struct device_attribute dattr; 1053628329d5SDmitry Torokhov size_t field_offset; 1054628329d5SDmitry Torokhov int field_length; 1055628329d5SDmitry Torokhov }; 1056628329d5SDmitry Torokhov 1057628329d5SDmitry Torokhov static ssize_t ims_pcu_attribute_show(struct device *dev, 1058628329d5SDmitry Torokhov struct device_attribute *dattr, 1059628329d5SDmitry Torokhov char *buf) 1060628329d5SDmitry Torokhov { 1061628329d5SDmitry Torokhov struct usb_interface *intf = to_usb_interface(dev); 1062628329d5SDmitry Torokhov struct ims_pcu *pcu = usb_get_intfdata(intf); 1063628329d5SDmitry Torokhov struct ims_pcu_attribute *attr = 1064628329d5SDmitry Torokhov container_of(dattr, struct ims_pcu_attribute, dattr); 1065628329d5SDmitry Torokhov char *field = (char *)pcu + attr->field_offset; 1066628329d5SDmitry Torokhov 1067628329d5SDmitry Torokhov return scnprintf(buf, PAGE_SIZE, "%.*s\n", attr->field_length, field); 1068628329d5SDmitry Torokhov } 1069628329d5SDmitry Torokhov 1070628329d5SDmitry Torokhov static ssize_t ims_pcu_attribute_store(struct device *dev, 1071628329d5SDmitry Torokhov struct device_attribute *dattr, 1072628329d5SDmitry Torokhov const char *buf, size_t count) 1073628329d5SDmitry Torokhov { 1074628329d5SDmitry Torokhov 1075628329d5SDmitry Torokhov struct usb_interface *intf = to_usb_interface(dev); 1076628329d5SDmitry Torokhov struct ims_pcu *pcu = usb_get_intfdata(intf); 1077628329d5SDmitry Torokhov struct ims_pcu_attribute *attr = 1078628329d5SDmitry Torokhov container_of(dattr, struct ims_pcu_attribute, dattr); 1079628329d5SDmitry Torokhov char *field = (char *)pcu + attr->field_offset; 1080628329d5SDmitry Torokhov size_t data_len; 1081628329d5SDmitry Torokhov int error; 1082628329d5SDmitry Torokhov 1083628329d5SDmitry Torokhov if (count > attr->field_length) 1084628329d5SDmitry Torokhov return -EINVAL; 1085628329d5SDmitry Torokhov 1086628329d5SDmitry Torokhov data_len = strnlen(buf, attr->field_length); 1087628329d5SDmitry Torokhov if (data_len > attr->field_length) 1088628329d5SDmitry Torokhov return -EINVAL; 1089628329d5SDmitry Torokhov 1090628329d5SDmitry Torokhov error = mutex_lock_interruptible(&pcu->cmd_mutex); 1091628329d5SDmitry Torokhov if (error) 1092628329d5SDmitry Torokhov return error; 1093628329d5SDmitry Torokhov 1094628329d5SDmitry Torokhov memset(field, 0, attr->field_length); 1095628329d5SDmitry Torokhov memcpy(field, buf, data_len); 1096628329d5SDmitry Torokhov 1097628329d5SDmitry Torokhov error = ims_pcu_set_info(pcu); 1098628329d5SDmitry Torokhov 1099628329d5SDmitry Torokhov /* 1100628329d5SDmitry Torokhov * Even if update failed, let's fetch the info again as we just 1101628329d5SDmitry Torokhov * clobbered one of the fields. 1102628329d5SDmitry Torokhov */ 1103628329d5SDmitry Torokhov ims_pcu_get_info(pcu); 1104628329d5SDmitry Torokhov 1105628329d5SDmitry Torokhov mutex_unlock(&pcu->cmd_mutex); 1106628329d5SDmitry Torokhov 1107628329d5SDmitry Torokhov return error < 0 ? error : count; 1108628329d5SDmitry Torokhov } 1109628329d5SDmitry Torokhov 1110628329d5SDmitry Torokhov #define IMS_PCU_ATTR(_field, _mode) \ 1111628329d5SDmitry Torokhov struct ims_pcu_attribute ims_pcu_attr_##_field = { \ 1112628329d5SDmitry Torokhov .dattr = __ATTR(_field, _mode, \ 1113628329d5SDmitry Torokhov ims_pcu_attribute_show, \ 1114628329d5SDmitry Torokhov ims_pcu_attribute_store), \ 1115628329d5SDmitry Torokhov .field_offset = offsetof(struct ims_pcu, _field), \ 1116628329d5SDmitry Torokhov .field_length = sizeof(((struct ims_pcu *)NULL)->_field), \ 1117628329d5SDmitry Torokhov } 1118628329d5SDmitry Torokhov 1119628329d5SDmitry Torokhov #define IMS_PCU_RO_ATTR(_field) \ 1120628329d5SDmitry Torokhov IMS_PCU_ATTR(_field, S_IRUGO) 1121628329d5SDmitry Torokhov #define IMS_PCU_RW_ATTR(_field) \ 1122628329d5SDmitry Torokhov IMS_PCU_ATTR(_field, S_IRUGO | S_IWUSR) 1123628329d5SDmitry Torokhov 1124628329d5SDmitry Torokhov static IMS_PCU_RW_ATTR(part_number); 1125628329d5SDmitry Torokhov static IMS_PCU_RW_ATTR(serial_number); 1126628329d5SDmitry Torokhov static IMS_PCU_RW_ATTR(date_of_manufacturing); 1127628329d5SDmitry Torokhov 1128628329d5SDmitry Torokhov static IMS_PCU_RO_ATTR(fw_version); 1129628329d5SDmitry Torokhov static IMS_PCU_RO_ATTR(bl_version); 1130628329d5SDmitry Torokhov static IMS_PCU_RO_ATTR(reset_reason); 1131628329d5SDmitry Torokhov 1132628329d5SDmitry Torokhov static ssize_t ims_pcu_reset_device(struct device *dev, 1133628329d5SDmitry Torokhov struct device_attribute *dattr, 1134628329d5SDmitry Torokhov const char *buf, size_t count) 1135628329d5SDmitry Torokhov { 1136628329d5SDmitry Torokhov static const u8 reset_byte = 1; 1137628329d5SDmitry Torokhov struct usb_interface *intf = to_usb_interface(dev); 1138628329d5SDmitry Torokhov struct ims_pcu *pcu = usb_get_intfdata(intf); 1139628329d5SDmitry Torokhov int value; 1140628329d5SDmitry Torokhov int error; 1141628329d5SDmitry Torokhov 1142628329d5SDmitry Torokhov error = kstrtoint(buf, 0, &value); 1143628329d5SDmitry Torokhov if (error) 1144628329d5SDmitry Torokhov return error; 1145628329d5SDmitry Torokhov 1146628329d5SDmitry Torokhov if (value != 1) 1147628329d5SDmitry Torokhov return -EINVAL; 1148628329d5SDmitry Torokhov 1149628329d5SDmitry Torokhov dev_info(pcu->dev, "Attempting to reset device\n"); 1150628329d5SDmitry Torokhov 1151628329d5SDmitry Torokhov error = ims_pcu_execute_command(pcu, PCU_RESET, &reset_byte, 1); 1152628329d5SDmitry Torokhov if (error) { 1153628329d5SDmitry Torokhov dev_info(pcu->dev, 1154628329d5SDmitry Torokhov "Failed to reset device, error: %d\n", 1155628329d5SDmitry Torokhov error); 1156628329d5SDmitry Torokhov return error; 1157628329d5SDmitry Torokhov } 1158628329d5SDmitry Torokhov 1159628329d5SDmitry Torokhov return count; 1160628329d5SDmitry Torokhov } 1161628329d5SDmitry Torokhov 1162628329d5SDmitry Torokhov static DEVICE_ATTR(reset_device, S_IWUSR, NULL, ims_pcu_reset_device); 1163628329d5SDmitry Torokhov 1164628329d5SDmitry Torokhov static ssize_t ims_pcu_update_firmware_store(struct device *dev, 1165628329d5SDmitry Torokhov struct device_attribute *dattr, 1166628329d5SDmitry Torokhov const char *buf, size_t count) 1167628329d5SDmitry Torokhov { 1168628329d5SDmitry Torokhov struct usb_interface *intf = to_usb_interface(dev); 1169628329d5SDmitry Torokhov struct ims_pcu *pcu = usb_get_intfdata(intf); 11703c2b9010SDmitry Torokhov const struct firmware *fw = NULL; 1171628329d5SDmitry Torokhov int value; 1172628329d5SDmitry Torokhov int error; 1173628329d5SDmitry Torokhov 1174628329d5SDmitry Torokhov error = kstrtoint(buf, 0, &value); 1175628329d5SDmitry Torokhov if (error) 1176628329d5SDmitry Torokhov return error; 1177628329d5SDmitry Torokhov 1178628329d5SDmitry Torokhov if (value != 1) 1179628329d5SDmitry Torokhov return -EINVAL; 1180628329d5SDmitry Torokhov 1181628329d5SDmitry Torokhov error = mutex_lock_interruptible(&pcu->cmd_mutex); 1182628329d5SDmitry Torokhov if (error) 1183628329d5SDmitry Torokhov return error; 1184628329d5SDmitry Torokhov 1185628329d5SDmitry Torokhov error = request_ihex_firmware(&fw, IMS_PCU_FIRMWARE_NAME, pcu->dev); 1186628329d5SDmitry Torokhov if (error) { 1187628329d5SDmitry Torokhov dev_err(pcu->dev, "Failed to request firmware %s, error: %d\n", 1188628329d5SDmitry Torokhov IMS_PCU_FIRMWARE_NAME, error); 1189628329d5SDmitry Torokhov goto out; 1190628329d5SDmitry Torokhov } 1191628329d5SDmitry Torokhov 1192628329d5SDmitry Torokhov /* 1193628329d5SDmitry Torokhov * If we are already in bootloader mode we can proceed with 1194628329d5SDmitry Torokhov * flashing the firmware. 1195628329d5SDmitry Torokhov * 1196628329d5SDmitry Torokhov * If we are in application mode, then we need to switch into 1197628329d5SDmitry Torokhov * bootloader mode, which will cause the device to disconnect 1198628329d5SDmitry Torokhov * and reconnect as different device. 1199628329d5SDmitry Torokhov */ 1200628329d5SDmitry Torokhov if (pcu->bootloader_mode) 1201628329d5SDmitry Torokhov error = ims_pcu_handle_firmware_update(pcu, fw); 1202628329d5SDmitry Torokhov else 1203628329d5SDmitry Torokhov error = ims_pcu_switch_to_bootloader(pcu); 1204628329d5SDmitry Torokhov 1205628329d5SDmitry Torokhov release_firmware(fw); 1206628329d5SDmitry Torokhov 1207628329d5SDmitry Torokhov out: 1208628329d5SDmitry Torokhov mutex_unlock(&pcu->cmd_mutex); 1209628329d5SDmitry Torokhov return error ?: count; 1210628329d5SDmitry Torokhov } 1211628329d5SDmitry Torokhov 1212628329d5SDmitry Torokhov static DEVICE_ATTR(update_firmware, S_IWUSR, 1213628329d5SDmitry Torokhov NULL, ims_pcu_update_firmware_store); 1214628329d5SDmitry Torokhov 1215628329d5SDmitry Torokhov static ssize_t 1216628329d5SDmitry Torokhov ims_pcu_update_firmware_status_show(struct device *dev, 1217628329d5SDmitry Torokhov struct device_attribute *dattr, 1218628329d5SDmitry Torokhov char *buf) 1219628329d5SDmitry Torokhov { 1220628329d5SDmitry Torokhov struct usb_interface *intf = to_usb_interface(dev); 1221628329d5SDmitry Torokhov struct ims_pcu *pcu = usb_get_intfdata(intf); 1222628329d5SDmitry Torokhov 1223628329d5SDmitry Torokhov return scnprintf(buf, PAGE_SIZE, "%d\n", pcu->update_firmware_status); 1224628329d5SDmitry Torokhov } 1225628329d5SDmitry Torokhov 1226628329d5SDmitry Torokhov static DEVICE_ATTR(update_firmware_status, S_IRUGO, 1227628329d5SDmitry Torokhov ims_pcu_update_firmware_status_show, NULL); 1228628329d5SDmitry Torokhov 1229628329d5SDmitry Torokhov static struct attribute *ims_pcu_attrs[] = { 1230628329d5SDmitry Torokhov &ims_pcu_attr_part_number.dattr.attr, 1231628329d5SDmitry Torokhov &ims_pcu_attr_serial_number.dattr.attr, 1232628329d5SDmitry Torokhov &ims_pcu_attr_date_of_manufacturing.dattr.attr, 1233628329d5SDmitry Torokhov &ims_pcu_attr_fw_version.dattr.attr, 1234628329d5SDmitry Torokhov &ims_pcu_attr_bl_version.dattr.attr, 1235628329d5SDmitry Torokhov &ims_pcu_attr_reset_reason.dattr.attr, 1236628329d5SDmitry Torokhov &dev_attr_reset_device.attr, 1237628329d5SDmitry Torokhov &dev_attr_update_firmware.attr, 1238628329d5SDmitry Torokhov &dev_attr_update_firmware_status.attr, 1239628329d5SDmitry Torokhov NULL 1240628329d5SDmitry Torokhov }; 1241628329d5SDmitry Torokhov 1242628329d5SDmitry Torokhov static umode_t ims_pcu_is_attr_visible(struct kobject *kobj, 1243628329d5SDmitry Torokhov struct attribute *attr, int n) 1244628329d5SDmitry Torokhov { 1245628329d5SDmitry Torokhov struct device *dev = container_of(kobj, struct device, kobj); 1246628329d5SDmitry Torokhov struct usb_interface *intf = to_usb_interface(dev); 1247628329d5SDmitry Torokhov struct ims_pcu *pcu = usb_get_intfdata(intf); 1248628329d5SDmitry Torokhov umode_t mode = attr->mode; 1249628329d5SDmitry Torokhov 1250628329d5SDmitry Torokhov if (pcu->bootloader_mode) { 1251628329d5SDmitry Torokhov if (attr != &dev_attr_update_firmware_status.attr && 1252628329d5SDmitry Torokhov attr != &dev_attr_update_firmware.attr && 1253628329d5SDmitry Torokhov attr != &dev_attr_reset_device.attr) { 1254628329d5SDmitry Torokhov mode = 0; 1255628329d5SDmitry Torokhov } 1256628329d5SDmitry Torokhov } else { 1257628329d5SDmitry Torokhov if (attr == &dev_attr_update_firmware_status.attr) 1258628329d5SDmitry Torokhov mode = 0; 1259628329d5SDmitry Torokhov } 1260628329d5SDmitry Torokhov 1261628329d5SDmitry Torokhov return mode; 1262628329d5SDmitry Torokhov } 1263628329d5SDmitry Torokhov 1264628329d5SDmitry Torokhov static struct attribute_group ims_pcu_attr_group = { 1265628329d5SDmitry Torokhov .is_visible = ims_pcu_is_attr_visible, 1266628329d5SDmitry Torokhov .attrs = ims_pcu_attrs, 1267628329d5SDmitry Torokhov }; 1268628329d5SDmitry Torokhov 1269e5fcd269SAndrey Smirnov /* Support for a separate OFN attribute group */ 1270e5fcd269SAndrey Smirnov 1271e5fcd269SAndrey Smirnov #define OFN_REG_RESULT_OFFSET 2 1272e5fcd269SAndrey Smirnov 1273e5fcd269SAndrey Smirnov static int ims_pcu_read_ofn_config(struct ims_pcu *pcu, u8 addr, u8 *data) 1274e5fcd269SAndrey Smirnov { 1275e5fcd269SAndrey Smirnov int error; 1276e5fcd269SAndrey Smirnov s16 result; 1277e5fcd269SAndrey Smirnov 1278e5fcd269SAndrey Smirnov error = ims_pcu_execute_command(pcu, OFN_GET_CONFIG, 1279e5fcd269SAndrey Smirnov &addr, sizeof(addr)); 1280e5fcd269SAndrey Smirnov if (error) 1281e5fcd269SAndrey Smirnov return error; 1282e5fcd269SAndrey Smirnov 1283e5fcd269SAndrey Smirnov result = (s16)get_unaligned_le16(pcu->cmd_buf + OFN_REG_RESULT_OFFSET); 1284e5fcd269SAndrey Smirnov if (result < 0) 1285e5fcd269SAndrey Smirnov return -EIO; 1286e5fcd269SAndrey Smirnov 1287e5fcd269SAndrey Smirnov /* We only need LSB */ 1288e5fcd269SAndrey Smirnov *data = pcu->cmd_buf[OFN_REG_RESULT_OFFSET]; 1289e5fcd269SAndrey Smirnov return 0; 1290e5fcd269SAndrey Smirnov } 1291e5fcd269SAndrey Smirnov 1292e5fcd269SAndrey Smirnov static int ims_pcu_write_ofn_config(struct ims_pcu *pcu, u8 addr, u8 data) 1293e5fcd269SAndrey Smirnov { 1294e5fcd269SAndrey Smirnov u8 buffer[] = { addr, data }; 1295e5fcd269SAndrey Smirnov int error; 1296e5fcd269SAndrey Smirnov s16 result; 1297e5fcd269SAndrey Smirnov 1298e5fcd269SAndrey Smirnov error = ims_pcu_execute_command(pcu, OFN_SET_CONFIG, 1299e5fcd269SAndrey Smirnov &buffer, sizeof(buffer)); 1300e5fcd269SAndrey Smirnov if (error) 1301e5fcd269SAndrey Smirnov return error; 1302e5fcd269SAndrey Smirnov 1303e5fcd269SAndrey Smirnov result = (s16)get_unaligned_le16(pcu->cmd_buf + OFN_REG_RESULT_OFFSET); 1304e5fcd269SAndrey Smirnov if (result < 0) 1305e5fcd269SAndrey Smirnov return -EIO; 1306e5fcd269SAndrey Smirnov 1307e5fcd269SAndrey Smirnov return 0; 1308e5fcd269SAndrey Smirnov } 1309e5fcd269SAndrey Smirnov 1310e5fcd269SAndrey Smirnov static ssize_t ims_pcu_ofn_reg_data_show(struct device *dev, 1311e5fcd269SAndrey Smirnov struct device_attribute *dattr, 1312e5fcd269SAndrey Smirnov char *buf) 1313e5fcd269SAndrey Smirnov { 1314e5fcd269SAndrey Smirnov struct usb_interface *intf = to_usb_interface(dev); 1315e5fcd269SAndrey Smirnov struct ims_pcu *pcu = usb_get_intfdata(intf); 1316e5fcd269SAndrey Smirnov int error; 1317e5fcd269SAndrey Smirnov u8 data; 1318e5fcd269SAndrey Smirnov 1319e5fcd269SAndrey Smirnov mutex_lock(&pcu->cmd_mutex); 1320e5fcd269SAndrey Smirnov error = ims_pcu_read_ofn_config(pcu, pcu->ofn_reg_addr, &data); 1321e5fcd269SAndrey Smirnov mutex_unlock(&pcu->cmd_mutex); 1322e5fcd269SAndrey Smirnov 1323e5fcd269SAndrey Smirnov if (error) 1324e5fcd269SAndrey Smirnov return error; 1325e5fcd269SAndrey Smirnov 1326e5fcd269SAndrey Smirnov return scnprintf(buf, PAGE_SIZE, "%x\n", data); 1327e5fcd269SAndrey Smirnov } 1328e5fcd269SAndrey Smirnov 1329e5fcd269SAndrey Smirnov static ssize_t ims_pcu_ofn_reg_data_store(struct device *dev, 1330e5fcd269SAndrey Smirnov struct device_attribute *dattr, 1331e5fcd269SAndrey Smirnov const char *buf, size_t count) 1332e5fcd269SAndrey Smirnov { 1333e5fcd269SAndrey Smirnov struct usb_interface *intf = to_usb_interface(dev); 1334e5fcd269SAndrey Smirnov struct ims_pcu *pcu = usb_get_intfdata(intf); 1335e5fcd269SAndrey Smirnov int error; 1336e5fcd269SAndrey Smirnov u8 value; 1337e5fcd269SAndrey Smirnov 1338e5fcd269SAndrey Smirnov error = kstrtou8(buf, 0, &value); 1339e5fcd269SAndrey Smirnov if (error) 1340e5fcd269SAndrey Smirnov return error; 1341e5fcd269SAndrey Smirnov 1342e5fcd269SAndrey Smirnov mutex_lock(&pcu->cmd_mutex); 1343e5fcd269SAndrey Smirnov error = ims_pcu_write_ofn_config(pcu, pcu->ofn_reg_addr, value); 1344e5fcd269SAndrey Smirnov mutex_unlock(&pcu->cmd_mutex); 1345e5fcd269SAndrey Smirnov 1346e5fcd269SAndrey Smirnov return error ?: count; 1347e5fcd269SAndrey Smirnov } 1348e5fcd269SAndrey Smirnov 1349e5fcd269SAndrey Smirnov static DEVICE_ATTR(reg_data, S_IRUGO | S_IWUSR, 1350e5fcd269SAndrey Smirnov ims_pcu_ofn_reg_data_show, ims_pcu_ofn_reg_data_store); 1351e5fcd269SAndrey Smirnov 1352e5fcd269SAndrey Smirnov static ssize_t ims_pcu_ofn_reg_addr_show(struct device *dev, 1353e5fcd269SAndrey Smirnov struct device_attribute *dattr, 1354e5fcd269SAndrey Smirnov char *buf) 1355e5fcd269SAndrey Smirnov { 1356e5fcd269SAndrey Smirnov struct usb_interface *intf = to_usb_interface(dev); 1357e5fcd269SAndrey Smirnov struct ims_pcu *pcu = usb_get_intfdata(intf); 1358e5fcd269SAndrey Smirnov int error; 1359e5fcd269SAndrey Smirnov 1360e5fcd269SAndrey Smirnov mutex_lock(&pcu->cmd_mutex); 1361e5fcd269SAndrey Smirnov error = scnprintf(buf, PAGE_SIZE, "%x\n", pcu->ofn_reg_addr); 1362e5fcd269SAndrey Smirnov mutex_unlock(&pcu->cmd_mutex); 1363e5fcd269SAndrey Smirnov 1364e5fcd269SAndrey Smirnov return error; 1365e5fcd269SAndrey Smirnov } 1366e5fcd269SAndrey Smirnov 1367e5fcd269SAndrey Smirnov static ssize_t ims_pcu_ofn_reg_addr_store(struct device *dev, 1368e5fcd269SAndrey Smirnov struct device_attribute *dattr, 1369e5fcd269SAndrey Smirnov const char *buf, size_t count) 1370e5fcd269SAndrey Smirnov { 1371e5fcd269SAndrey Smirnov struct usb_interface *intf = to_usb_interface(dev); 1372e5fcd269SAndrey Smirnov struct ims_pcu *pcu = usb_get_intfdata(intf); 1373e5fcd269SAndrey Smirnov int error; 1374e5fcd269SAndrey Smirnov u8 value; 1375e5fcd269SAndrey Smirnov 1376e5fcd269SAndrey Smirnov error = kstrtou8(buf, 0, &value); 1377e5fcd269SAndrey Smirnov if (error) 1378e5fcd269SAndrey Smirnov return error; 1379e5fcd269SAndrey Smirnov 1380e5fcd269SAndrey Smirnov mutex_lock(&pcu->cmd_mutex); 1381e5fcd269SAndrey Smirnov pcu->ofn_reg_addr = value; 1382e5fcd269SAndrey Smirnov mutex_unlock(&pcu->cmd_mutex); 1383e5fcd269SAndrey Smirnov 1384e5fcd269SAndrey Smirnov return error ?: count; 1385e5fcd269SAndrey Smirnov } 1386e5fcd269SAndrey Smirnov 1387e5fcd269SAndrey Smirnov static DEVICE_ATTR(reg_addr, S_IRUGO | S_IWUSR, 1388e5fcd269SAndrey Smirnov ims_pcu_ofn_reg_addr_show, ims_pcu_ofn_reg_addr_store); 1389e5fcd269SAndrey Smirnov 1390e5fcd269SAndrey Smirnov struct ims_pcu_ofn_bit_attribute { 1391e5fcd269SAndrey Smirnov struct device_attribute dattr; 1392e5fcd269SAndrey Smirnov u8 addr; 1393e5fcd269SAndrey Smirnov u8 nr; 1394e5fcd269SAndrey Smirnov }; 1395e5fcd269SAndrey Smirnov 1396e5fcd269SAndrey Smirnov static ssize_t ims_pcu_ofn_bit_show(struct device *dev, 1397e5fcd269SAndrey Smirnov struct device_attribute *dattr, 1398e5fcd269SAndrey Smirnov char *buf) 1399e5fcd269SAndrey Smirnov { 1400e5fcd269SAndrey Smirnov struct usb_interface *intf = to_usb_interface(dev); 1401e5fcd269SAndrey Smirnov struct ims_pcu *pcu = usb_get_intfdata(intf); 1402e5fcd269SAndrey Smirnov struct ims_pcu_ofn_bit_attribute *attr = 1403e5fcd269SAndrey Smirnov container_of(dattr, struct ims_pcu_ofn_bit_attribute, dattr); 1404e5fcd269SAndrey Smirnov int error; 1405e5fcd269SAndrey Smirnov u8 data; 1406e5fcd269SAndrey Smirnov 1407e5fcd269SAndrey Smirnov mutex_lock(&pcu->cmd_mutex); 1408e5fcd269SAndrey Smirnov error = ims_pcu_read_ofn_config(pcu, attr->addr, &data); 1409e5fcd269SAndrey Smirnov mutex_unlock(&pcu->cmd_mutex); 1410e5fcd269SAndrey Smirnov 1411e5fcd269SAndrey Smirnov if (error) 1412e5fcd269SAndrey Smirnov return error; 1413e5fcd269SAndrey Smirnov 1414e5fcd269SAndrey Smirnov return scnprintf(buf, PAGE_SIZE, "%d\n", !!(data & (1 << attr->nr))); 1415e5fcd269SAndrey Smirnov } 1416e5fcd269SAndrey Smirnov 1417e5fcd269SAndrey Smirnov static ssize_t ims_pcu_ofn_bit_store(struct device *dev, 1418e5fcd269SAndrey Smirnov struct device_attribute *dattr, 1419e5fcd269SAndrey Smirnov const char *buf, size_t count) 1420e5fcd269SAndrey Smirnov { 1421e5fcd269SAndrey Smirnov struct usb_interface *intf = to_usb_interface(dev); 1422e5fcd269SAndrey Smirnov struct ims_pcu *pcu = usb_get_intfdata(intf); 1423e5fcd269SAndrey Smirnov struct ims_pcu_ofn_bit_attribute *attr = 1424e5fcd269SAndrey Smirnov container_of(dattr, struct ims_pcu_ofn_bit_attribute, dattr); 1425e5fcd269SAndrey Smirnov int error; 1426e5fcd269SAndrey Smirnov int value; 1427e5fcd269SAndrey Smirnov u8 data; 1428e5fcd269SAndrey Smirnov 1429e5fcd269SAndrey Smirnov error = kstrtoint(buf, 0, &value); 1430e5fcd269SAndrey Smirnov if (error) 1431e5fcd269SAndrey Smirnov return error; 1432e5fcd269SAndrey Smirnov 1433e5fcd269SAndrey Smirnov if (value > 1) 1434e5fcd269SAndrey Smirnov return -EINVAL; 1435e5fcd269SAndrey Smirnov 1436e5fcd269SAndrey Smirnov mutex_lock(&pcu->cmd_mutex); 1437e5fcd269SAndrey Smirnov 1438e5fcd269SAndrey Smirnov error = ims_pcu_read_ofn_config(pcu, attr->addr, &data); 1439e5fcd269SAndrey Smirnov if (!error) { 1440e5fcd269SAndrey Smirnov if (value) 1441e5fcd269SAndrey Smirnov data |= 1U << attr->nr; 1442e5fcd269SAndrey Smirnov else 1443e5fcd269SAndrey Smirnov data &= ~(1U << attr->nr); 1444e5fcd269SAndrey Smirnov 1445e5fcd269SAndrey Smirnov error = ims_pcu_write_ofn_config(pcu, attr->addr, data); 1446e5fcd269SAndrey Smirnov } 1447e5fcd269SAndrey Smirnov 1448e5fcd269SAndrey Smirnov mutex_unlock(&pcu->cmd_mutex); 1449e5fcd269SAndrey Smirnov 1450e5fcd269SAndrey Smirnov return error ?: count; 1451e5fcd269SAndrey Smirnov } 1452e5fcd269SAndrey Smirnov 1453e5fcd269SAndrey Smirnov #define IMS_PCU_OFN_BIT_ATTR(_field, _addr, _nr) \ 1454e5fcd269SAndrey Smirnov struct ims_pcu_ofn_bit_attribute ims_pcu_ofn_attr_##_field = { \ 1455e5fcd269SAndrey Smirnov .dattr = __ATTR(_field, S_IWUSR | S_IRUGO, \ 1456e5fcd269SAndrey Smirnov ims_pcu_ofn_bit_show, ims_pcu_ofn_bit_store), \ 1457e5fcd269SAndrey Smirnov .addr = _addr, \ 1458e5fcd269SAndrey Smirnov .nr = _nr, \ 1459e5fcd269SAndrey Smirnov } 1460e5fcd269SAndrey Smirnov 1461e5fcd269SAndrey Smirnov static IMS_PCU_OFN_BIT_ATTR(engine_enable, 0x60, 7); 1462e5fcd269SAndrey Smirnov static IMS_PCU_OFN_BIT_ATTR(speed_enable, 0x60, 6); 1463e5fcd269SAndrey Smirnov static IMS_PCU_OFN_BIT_ATTR(assert_enable, 0x60, 5); 1464e5fcd269SAndrey Smirnov static IMS_PCU_OFN_BIT_ATTR(xyquant_enable, 0x60, 4); 1465e5fcd269SAndrey Smirnov static IMS_PCU_OFN_BIT_ATTR(xyscale_enable, 0x60, 1); 1466e5fcd269SAndrey Smirnov 1467e5fcd269SAndrey Smirnov static IMS_PCU_OFN_BIT_ATTR(scale_x2, 0x63, 6); 1468e5fcd269SAndrey Smirnov static IMS_PCU_OFN_BIT_ATTR(scale_y2, 0x63, 7); 1469e5fcd269SAndrey Smirnov 1470e5fcd269SAndrey Smirnov static struct attribute *ims_pcu_ofn_attrs[] = { 1471e5fcd269SAndrey Smirnov &dev_attr_reg_data.attr, 1472e5fcd269SAndrey Smirnov &dev_attr_reg_addr.attr, 1473e5fcd269SAndrey Smirnov &ims_pcu_ofn_attr_engine_enable.dattr.attr, 1474e5fcd269SAndrey Smirnov &ims_pcu_ofn_attr_speed_enable.dattr.attr, 1475e5fcd269SAndrey Smirnov &ims_pcu_ofn_attr_assert_enable.dattr.attr, 1476e5fcd269SAndrey Smirnov &ims_pcu_ofn_attr_xyquant_enable.dattr.attr, 1477e5fcd269SAndrey Smirnov &ims_pcu_ofn_attr_xyscale_enable.dattr.attr, 1478e5fcd269SAndrey Smirnov &ims_pcu_ofn_attr_scale_x2.dattr.attr, 1479e5fcd269SAndrey Smirnov &ims_pcu_ofn_attr_scale_y2.dattr.attr, 1480e5fcd269SAndrey Smirnov NULL 1481e5fcd269SAndrey Smirnov }; 1482e5fcd269SAndrey Smirnov 1483e5fcd269SAndrey Smirnov static struct attribute_group ims_pcu_ofn_attr_group = { 1484e5fcd269SAndrey Smirnov .name = "ofn", 1485e5fcd269SAndrey Smirnov .attrs = ims_pcu_ofn_attrs, 1486e5fcd269SAndrey Smirnov }; 1487e5fcd269SAndrey Smirnov 1488628329d5SDmitry Torokhov static void ims_pcu_irq(struct urb *urb) 1489628329d5SDmitry Torokhov { 1490628329d5SDmitry Torokhov struct ims_pcu *pcu = urb->context; 1491628329d5SDmitry Torokhov int retval, status; 1492628329d5SDmitry Torokhov 1493628329d5SDmitry Torokhov status = urb->status; 1494628329d5SDmitry Torokhov 1495628329d5SDmitry Torokhov switch (status) { 1496628329d5SDmitry Torokhov case 0: 1497628329d5SDmitry Torokhov /* success */ 1498628329d5SDmitry Torokhov break; 1499628329d5SDmitry Torokhov case -ECONNRESET: 1500628329d5SDmitry Torokhov case -ENOENT: 1501628329d5SDmitry Torokhov case -ESHUTDOWN: 1502628329d5SDmitry Torokhov /* this urb is terminated, clean up */ 1503628329d5SDmitry Torokhov dev_dbg(pcu->dev, "%s - urb shutting down with status: %d\n", 1504628329d5SDmitry Torokhov __func__, status); 1505628329d5SDmitry Torokhov return; 1506628329d5SDmitry Torokhov default: 1507628329d5SDmitry Torokhov dev_dbg(pcu->dev, "%s - nonzero urb status received: %d\n", 1508628329d5SDmitry Torokhov __func__, status); 1509628329d5SDmitry Torokhov goto exit; 1510628329d5SDmitry Torokhov } 1511628329d5SDmitry Torokhov 1512628329d5SDmitry Torokhov dev_dbg(pcu->dev, "%s: received %d: %*ph\n", __func__, 1513628329d5SDmitry Torokhov urb->actual_length, urb->actual_length, pcu->urb_in_buf); 1514628329d5SDmitry Torokhov 1515628329d5SDmitry Torokhov if (urb == pcu->urb_in) 1516628329d5SDmitry Torokhov ims_pcu_process_data(pcu, urb); 1517628329d5SDmitry Torokhov 1518628329d5SDmitry Torokhov exit: 1519628329d5SDmitry Torokhov retval = usb_submit_urb(urb, GFP_ATOMIC); 1520628329d5SDmitry Torokhov if (retval && retval != -ENODEV) 1521628329d5SDmitry Torokhov dev_err(pcu->dev, "%s - usb_submit_urb failed with result %d\n", 1522628329d5SDmitry Torokhov __func__, retval); 1523628329d5SDmitry Torokhov } 1524628329d5SDmitry Torokhov 1525628329d5SDmitry Torokhov static int ims_pcu_buffers_alloc(struct ims_pcu *pcu) 1526628329d5SDmitry Torokhov { 1527628329d5SDmitry Torokhov int error; 1528628329d5SDmitry Torokhov 1529628329d5SDmitry Torokhov pcu->urb_in_buf = usb_alloc_coherent(pcu->udev, pcu->max_in_size, 1530628329d5SDmitry Torokhov GFP_KERNEL, &pcu->read_dma); 1531628329d5SDmitry Torokhov if (!pcu->urb_in_buf) { 1532628329d5SDmitry Torokhov dev_err(pcu->dev, 1533628329d5SDmitry Torokhov "Failed to allocate memory for read buffer\n"); 1534628329d5SDmitry Torokhov return -ENOMEM; 1535628329d5SDmitry Torokhov } 1536628329d5SDmitry Torokhov 1537628329d5SDmitry Torokhov pcu->urb_in = usb_alloc_urb(0, GFP_KERNEL); 1538628329d5SDmitry Torokhov if (!pcu->urb_in) { 1539628329d5SDmitry Torokhov dev_err(pcu->dev, "Failed to allocate input URB\n"); 1540628329d5SDmitry Torokhov error = -ENOMEM; 1541628329d5SDmitry Torokhov goto err_free_urb_in_buf; 1542628329d5SDmitry Torokhov } 1543628329d5SDmitry Torokhov 1544628329d5SDmitry Torokhov pcu->urb_in->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; 1545628329d5SDmitry Torokhov pcu->urb_in->transfer_dma = pcu->read_dma; 1546628329d5SDmitry Torokhov 1547628329d5SDmitry Torokhov usb_fill_bulk_urb(pcu->urb_in, pcu->udev, 1548628329d5SDmitry Torokhov usb_rcvbulkpipe(pcu->udev, 1549628329d5SDmitry Torokhov pcu->ep_in->bEndpointAddress), 1550628329d5SDmitry Torokhov pcu->urb_in_buf, pcu->max_in_size, 1551628329d5SDmitry Torokhov ims_pcu_irq, pcu); 1552628329d5SDmitry Torokhov 1553628329d5SDmitry Torokhov /* 1554628329d5SDmitry Torokhov * We are using usb_bulk_msg() for sending so there is no point 1555628329d5SDmitry Torokhov * in allocating memory with usb_alloc_coherent(). 1556628329d5SDmitry Torokhov */ 1557628329d5SDmitry Torokhov pcu->urb_out_buf = kmalloc(pcu->max_out_size, GFP_KERNEL); 1558628329d5SDmitry Torokhov if (!pcu->urb_out_buf) { 1559628329d5SDmitry Torokhov dev_err(pcu->dev, "Failed to allocate memory for write buffer\n"); 1560628329d5SDmitry Torokhov error = -ENOMEM; 1561628329d5SDmitry Torokhov goto err_free_in_urb; 1562628329d5SDmitry Torokhov } 1563628329d5SDmitry Torokhov 1564628329d5SDmitry Torokhov pcu->urb_ctrl_buf = usb_alloc_coherent(pcu->udev, pcu->max_ctrl_size, 1565628329d5SDmitry Torokhov GFP_KERNEL, &pcu->ctrl_dma); 1566628329d5SDmitry Torokhov if (!pcu->urb_ctrl_buf) { 1567628329d5SDmitry Torokhov dev_err(pcu->dev, 1568628329d5SDmitry Torokhov "Failed to allocate memory for read buffer\n"); 1569628329d5SDmitry Torokhov goto err_free_urb_out_buf; 1570628329d5SDmitry Torokhov } 1571628329d5SDmitry Torokhov 1572628329d5SDmitry Torokhov pcu->urb_ctrl = usb_alloc_urb(0, GFP_KERNEL); 1573628329d5SDmitry Torokhov if (!pcu->urb_ctrl) { 1574628329d5SDmitry Torokhov dev_err(pcu->dev, "Failed to allocate input URB\n"); 1575628329d5SDmitry Torokhov error = -ENOMEM; 1576628329d5SDmitry Torokhov goto err_free_urb_ctrl_buf; 1577628329d5SDmitry Torokhov } 1578628329d5SDmitry Torokhov 1579628329d5SDmitry Torokhov pcu->urb_ctrl->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; 1580628329d5SDmitry Torokhov pcu->urb_ctrl->transfer_dma = pcu->ctrl_dma; 1581628329d5SDmitry Torokhov 1582628329d5SDmitry Torokhov usb_fill_int_urb(pcu->urb_ctrl, pcu->udev, 1583628329d5SDmitry Torokhov usb_rcvintpipe(pcu->udev, 1584628329d5SDmitry Torokhov pcu->ep_ctrl->bEndpointAddress), 1585628329d5SDmitry Torokhov pcu->urb_ctrl_buf, pcu->max_ctrl_size, 1586628329d5SDmitry Torokhov ims_pcu_irq, pcu, pcu->ep_ctrl->bInterval); 1587628329d5SDmitry Torokhov 1588628329d5SDmitry Torokhov return 0; 1589628329d5SDmitry Torokhov 1590628329d5SDmitry Torokhov err_free_urb_ctrl_buf: 1591628329d5SDmitry Torokhov usb_free_coherent(pcu->udev, pcu->max_ctrl_size, 1592628329d5SDmitry Torokhov pcu->urb_ctrl_buf, pcu->ctrl_dma); 1593628329d5SDmitry Torokhov err_free_urb_out_buf: 1594628329d5SDmitry Torokhov kfree(pcu->urb_out_buf); 1595628329d5SDmitry Torokhov err_free_in_urb: 1596628329d5SDmitry Torokhov usb_free_urb(pcu->urb_in); 1597628329d5SDmitry Torokhov err_free_urb_in_buf: 1598628329d5SDmitry Torokhov usb_free_coherent(pcu->udev, pcu->max_in_size, 1599628329d5SDmitry Torokhov pcu->urb_in_buf, pcu->read_dma); 1600628329d5SDmitry Torokhov return error; 1601628329d5SDmitry Torokhov } 1602628329d5SDmitry Torokhov 1603628329d5SDmitry Torokhov static void ims_pcu_buffers_free(struct ims_pcu *pcu) 1604628329d5SDmitry Torokhov { 1605628329d5SDmitry Torokhov usb_kill_urb(pcu->urb_in); 1606628329d5SDmitry Torokhov usb_free_urb(pcu->urb_in); 1607628329d5SDmitry Torokhov 1608628329d5SDmitry Torokhov usb_free_coherent(pcu->udev, pcu->max_out_size, 1609628329d5SDmitry Torokhov pcu->urb_in_buf, pcu->read_dma); 1610628329d5SDmitry Torokhov 1611628329d5SDmitry Torokhov kfree(pcu->urb_out_buf); 1612628329d5SDmitry Torokhov 1613628329d5SDmitry Torokhov usb_kill_urb(pcu->urb_ctrl); 1614628329d5SDmitry Torokhov usb_free_urb(pcu->urb_ctrl); 1615628329d5SDmitry Torokhov 1616628329d5SDmitry Torokhov usb_free_coherent(pcu->udev, pcu->max_ctrl_size, 1617628329d5SDmitry Torokhov pcu->urb_ctrl_buf, pcu->ctrl_dma); 1618628329d5SDmitry Torokhov } 1619628329d5SDmitry Torokhov 1620628329d5SDmitry Torokhov static const struct usb_cdc_union_desc * 1621628329d5SDmitry Torokhov ims_pcu_get_cdc_union_desc(struct usb_interface *intf) 1622628329d5SDmitry Torokhov { 1623628329d5SDmitry Torokhov const void *buf = intf->altsetting->extra; 1624628329d5SDmitry Torokhov size_t buflen = intf->altsetting->extralen; 1625628329d5SDmitry Torokhov struct usb_cdc_union_desc *union_desc; 1626628329d5SDmitry Torokhov 1627628329d5SDmitry Torokhov if (!buf) { 1628628329d5SDmitry Torokhov dev_err(&intf->dev, "Missing descriptor data\n"); 1629628329d5SDmitry Torokhov return NULL; 1630628329d5SDmitry Torokhov } 1631628329d5SDmitry Torokhov 1632628329d5SDmitry Torokhov if (!buflen) { 1633628329d5SDmitry Torokhov dev_err(&intf->dev, "Zero length descriptor\n"); 1634628329d5SDmitry Torokhov return NULL; 1635628329d5SDmitry Torokhov } 1636628329d5SDmitry Torokhov 1637628329d5SDmitry Torokhov while (buflen > 0) { 1638628329d5SDmitry Torokhov union_desc = (struct usb_cdc_union_desc *)buf; 1639628329d5SDmitry Torokhov 1640628329d5SDmitry Torokhov if (union_desc->bDescriptorType == USB_DT_CS_INTERFACE && 1641628329d5SDmitry Torokhov union_desc->bDescriptorSubType == USB_CDC_UNION_TYPE) { 1642628329d5SDmitry Torokhov dev_dbg(&intf->dev, "Found union header\n"); 1643628329d5SDmitry Torokhov return union_desc; 1644628329d5SDmitry Torokhov } 1645628329d5SDmitry Torokhov 1646628329d5SDmitry Torokhov buflen -= union_desc->bLength; 1647628329d5SDmitry Torokhov buf += union_desc->bLength; 1648628329d5SDmitry Torokhov } 1649628329d5SDmitry Torokhov 1650628329d5SDmitry Torokhov dev_err(&intf->dev, "Missing CDC union descriptor\n"); 1651628329d5SDmitry Torokhov return NULL; 1652628329d5SDmitry Torokhov } 1653628329d5SDmitry Torokhov 1654628329d5SDmitry Torokhov static int ims_pcu_parse_cdc_data(struct usb_interface *intf, struct ims_pcu *pcu) 1655628329d5SDmitry Torokhov { 1656628329d5SDmitry Torokhov const struct usb_cdc_union_desc *union_desc; 1657628329d5SDmitry Torokhov struct usb_host_interface *alt; 1658628329d5SDmitry Torokhov 1659628329d5SDmitry Torokhov union_desc = ims_pcu_get_cdc_union_desc(intf); 1660628329d5SDmitry Torokhov if (!union_desc) 1661628329d5SDmitry Torokhov return -EINVAL; 1662628329d5SDmitry Torokhov 1663628329d5SDmitry Torokhov pcu->ctrl_intf = usb_ifnum_to_if(pcu->udev, 1664628329d5SDmitry Torokhov union_desc->bMasterInterface0); 1665628329d5SDmitry Torokhov 1666628329d5SDmitry Torokhov alt = pcu->ctrl_intf->cur_altsetting; 1667628329d5SDmitry Torokhov pcu->ep_ctrl = &alt->endpoint[0].desc; 1668628329d5SDmitry Torokhov pcu->max_ctrl_size = usb_endpoint_maxp(pcu->ep_ctrl); 1669628329d5SDmitry Torokhov 1670628329d5SDmitry Torokhov pcu->data_intf = usb_ifnum_to_if(pcu->udev, 1671628329d5SDmitry Torokhov union_desc->bSlaveInterface0); 1672628329d5SDmitry Torokhov 1673628329d5SDmitry Torokhov alt = pcu->data_intf->cur_altsetting; 1674628329d5SDmitry Torokhov if (alt->desc.bNumEndpoints != 2) { 1675628329d5SDmitry Torokhov dev_err(pcu->dev, 1676628329d5SDmitry Torokhov "Incorrect number of endpoints on data interface (%d)\n", 1677628329d5SDmitry Torokhov alt->desc.bNumEndpoints); 1678628329d5SDmitry Torokhov return -EINVAL; 1679628329d5SDmitry Torokhov } 1680628329d5SDmitry Torokhov 1681628329d5SDmitry Torokhov pcu->ep_out = &alt->endpoint[0].desc; 1682628329d5SDmitry Torokhov if (!usb_endpoint_is_bulk_out(pcu->ep_out)) { 1683628329d5SDmitry Torokhov dev_err(pcu->dev, 1684628329d5SDmitry Torokhov "First endpoint on data interface is not BULK OUT\n"); 1685628329d5SDmitry Torokhov return -EINVAL; 1686628329d5SDmitry Torokhov } 1687628329d5SDmitry Torokhov 1688628329d5SDmitry Torokhov pcu->max_out_size = usb_endpoint_maxp(pcu->ep_out); 1689628329d5SDmitry Torokhov if (pcu->max_out_size < 8) { 1690628329d5SDmitry Torokhov dev_err(pcu->dev, 1691628329d5SDmitry Torokhov "Max OUT packet size is too small (%zd)\n", 1692628329d5SDmitry Torokhov pcu->max_out_size); 1693628329d5SDmitry Torokhov return -EINVAL; 1694628329d5SDmitry Torokhov } 1695628329d5SDmitry Torokhov 1696628329d5SDmitry Torokhov pcu->ep_in = &alt->endpoint[1].desc; 1697628329d5SDmitry Torokhov if (!usb_endpoint_is_bulk_in(pcu->ep_in)) { 1698628329d5SDmitry Torokhov dev_err(pcu->dev, 1699628329d5SDmitry Torokhov "Second endpoint on data interface is not BULK IN\n"); 1700628329d5SDmitry Torokhov return -EINVAL; 1701628329d5SDmitry Torokhov } 1702628329d5SDmitry Torokhov 1703628329d5SDmitry Torokhov pcu->max_in_size = usb_endpoint_maxp(pcu->ep_in); 1704628329d5SDmitry Torokhov if (pcu->max_in_size < 8) { 1705628329d5SDmitry Torokhov dev_err(pcu->dev, 1706628329d5SDmitry Torokhov "Max IN packet size is too small (%zd)\n", 1707628329d5SDmitry Torokhov pcu->max_in_size); 1708628329d5SDmitry Torokhov return -EINVAL; 1709628329d5SDmitry Torokhov } 1710628329d5SDmitry Torokhov 1711628329d5SDmitry Torokhov return 0; 1712628329d5SDmitry Torokhov } 1713628329d5SDmitry Torokhov 1714628329d5SDmitry Torokhov static int ims_pcu_start_io(struct ims_pcu *pcu) 1715628329d5SDmitry Torokhov { 1716628329d5SDmitry Torokhov int error; 1717628329d5SDmitry Torokhov 1718628329d5SDmitry Torokhov error = usb_submit_urb(pcu->urb_ctrl, GFP_KERNEL); 1719628329d5SDmitry Torokhov if (error) { 1720628329d5SDmitry Torokhov dev_err(pcu->dev, 1721628329d5SDmitry Torokhov "Failed to start control IO - usb_submit_urb failed with result: %d\n", 1722628329d5SDmitry Torokhov error); 1723628329d5SDmitry Torokhov return -EIO; 1724628329d5SDmitry Torokhov } 1725628329d5SDmitry Torokhov 1726628329d5SDmitry Torokhov error = usb_submit_urb(pcu->urb_in, GFP_KERNEL); 1727628329d5SDmitry Torokhov if (error) { 1728628329d5SDmitry Torokhov dev_err(pcu->dev, 1729628329d5SDmitry Torokhov "Failed to start IO - usb_submit_urb failed with result: %d\n", 1730628329d5SDmitry Torokhov error); 1731628329d5SDmitry Torokhov usb_kill_urb(pcu->urb_ctrl); 1732628329d5SDmitry Torokhov return -EIO; 1733628329d5SDmitry Torokhov } 1734628329d5SDmitry Torokhov 1735628329d5SDmitry Torokhov return 0; 1736628329d5SDmitry Torokhov } 1737628329d5SDmitry Torokhov 1738628329d5SDmitry Torokhov static void ims_pcu_stop_io(struct ims_pcu *pcu) 1739628329d5SDmitry Torokhov { 1740628329d5SDmitry Torokhov usb_kill_urb(pcu->urb_in); 1741628329d5SDmitry Torokhov usb_kill_urb(pcu->urb_ctrl); 1742628329d5SDmitry Torokhov } 1743628329d5SDmitry Torokhov 1744628329d5SDmitry Torokhov static int ims_pcu_line_setup(struct ims_pcu *pcu) 1745628329d5SDmitry Torokhov { 1746628329d5SDmitry Torokhov struct usb_host_interface *interface = pcu->ctrl_intf->cur_altsetting; 1747628329d5SDmitry Torokhov struct usb_cdc_line_coding *line = (void *)pcu->cmd_buf; 1748628329d5SDmitry Torokhov int error; 1749628329d5SDmitry Torokhov 1750628329d5SDmitry Torokhov memset(line, 0, sizeof(*line)); 1751628329d5SDmitry Torokhov line->dwDTERate = cpu_to_le32(57600); 1752628329d5SDmitry Torokhov line->bDataBits = 8; 1753628329d5SDmitry Torokhov 1754628329d5SDmitry Torokhov error = usb_control_msg(pcu->udev, usb_sndctrlpipe(pcu->udev, 0), 1755628329d5SDmitry Torokhov USB_CDC_REQ_SET_LINE_CODING, 1756628329d5SDmitry Torokhov USB_TYPE_CLASS | USB_RECIP_INTERFACE, 1757628329d5SDmitry Torokhov 0, interface->desc.bInterfaceNumber, 1758628329d5SDmitry Torokhov line, sizeof(struct usb_cdc_line_coding), 1759628329d5SDmitry Torokhov 5000); 1760628329d5SDmitry Torokhov if (error < 0) { 1761628329d5SDmitry Torokhov dev_err(pcu->dev, "Failed to set line coding, error: %d\n", 1762628329d5SDmitry Torokhov error); 1763628329d5SDmitry Torokhov return error; 1764628329d5SDmitry Torokhov } 1765628329d5SDmitry Torokhov 1766628329d5SDmitry Torokhov error = usb_control_msg(pcu->udev, usb_sndctrlpipe(pcu->udev, 0), 1767628329d5SDmitry Torokhov USB_CDC_REQ_SET_CONTROL_LINE_STATE, 1768628329d5SDmitry Torokhov USB_TYPE_CLASS | USB_RECIP_INTERFACE, 1769628329d5SDmitry Torokhov 0x03, interface->desc.bInterfaceNumber, 1770628329d5SDmitry Torokhov NULL, 0, 5000); 1771628329d5SDmitry Torokhov if (error < 0) { 1772628329d5SDmitry Torokhov dev_err(pcu->dev, "Failed to set line state, error: %d\n", 1773628329d5SDmitry Torokhov error); 1774628329d5SDmitry Torokhov return error; 1775628329d5SDmitry Torokhov } 1776628329d5SDmitry Torokhov 1777628329d5SDmitry Torokhov return 0; 1778628329d5SDmitry Torokhov } 1779628329d5SDmitry Torokhov 1780628329d5SDmitry Torokhov static int ims_pcu_get_device_info(struct ims_pcu *pcu) 1781628329d5SDmitry Torokhov { 1782628329d5SDmitry Torokhov int error; 1783628329d5SDmitry Torokhov 1784628329d5SDmitry Torokhov error = ims_pcu_get_info(pcu); 1785628329d5SDmitry Torokhov if (error) 1786628329d5SDmitry Torokhov return error; 1787628329d5SDmitry Torokhov 1788628329d5SDmitry Torokhov error = ims_pcu_execute_query(pcu, GET_FW_VERSION); 1789628329d5SDmitry Torokhov if (error) { 1790628329d5SDmitry Torokhov dev_err(pcu->dev, 1791628329d5SDmitry Torokhov "GET_FW_VERSION command failed, error: %d\n", error); 1792628329d5SDmitry Torokhov return error; 1793628329d5SDmitry Torokhov } 1794628329d5SDmitry Torokhov 1795628329d5SDmitry Torokhov snprintf(pcu->fw_version, sizeof(pcu->fw_version), 1796628329d5SDmitry Torokhov "%02d%02d%02d%02d.%c%c", 1797628329d5SDmitry Torokhov pcu->cmd_buf[2], pcu->cmd_buf[3], pcu->cmd_buf[4], pcu->cmd_buf[5], 1798628329d5SDmitry Torokhov pcu->cmd_buf[6], pcu->cmd_buf[7]); 1799628329d5SDmitry Torokhov 1800628329d5SDmitry Torokhov error = ims_pcu_execute_query(pcu, GET_BL_VERSION); 1801628329d5SDmitry Torokhov if (error) { 1802628329d5SDmitry Torokhov dev_err(pcu->dev, 1803628329d5SDmitry Torokhov "GET_BL_VERSION command failed, error: %d\n", error); 1804628329d5SDmitry Torokhov return error; 1805628329d5SDmitry Torokhov } 1806628329d5SDmitry Torokhov 1807628329d5SDmitry Torokhov snprintf(pcu->bl_version, sizeof(pcu->bl_version), 1808628329d5SDmitry Torokhov "%02d%02d%02d%02d.%c%c", 1809628329d5SDmitry Torokhov pcu->cmd_buf[2], pcu->cmd_buf[3], pcu->cmd_buf[4], pcu->cmd_buf[5], 1810628329d5SDmitry Torokhov pcu->cmd_buf[6], pcu->cmd_buf[7]); 1811628329d5SDmitry Torokhov 1812628329d5SDmitry Torokhov error = ims_pcu_execute_query(pcu, RESET_REASON); 1813628329d5SDmitry Torokhov if (error) { 1814628329d5SDmitry Torokhov dev_err(pcu->dev, 1815628329d5SDmitry Torokhov "RESET_REASON command failed, error: %d\n", error); 1816628329d5SDmitry Torokhov return error; 1817628329d5SDmitry Torokhov } 1818628329d5SDmitry Torokhov 1819628329d5SDmitry Torokhov snprintf(pcu->reset_reason, sizeof(pcu->reset_reason), 1820628329d5SDmitry Torokhov "%02x", pcu->cmd_buf[IMS_PCU_DATA_OFFSET]); 1821628329d5SDmitry Torokhov 1822628329d5SDmitry Torokhov dev_dbg(pcu->dev, 1823628329d5SDmitry Torokhov "P/N: %s, MD: %s, S/N: %s, FW: %s, BL: %s, RR: %s\n", 1824628329d5SDmitry Torokhov pcu->part_number, 1825628329d5SDmitry Torokhov pcu->date_of_manufacturing, 1826628329d5SDmitry Torokhov pcu->serial_number, 1827628329d5SDmitry Torokhov pcu->fw_version, 1828628329d5SDmitry Torokhov pcu->bl_version, 1829628329d5SDmitry Torokhov pcu->reset_reason); 1830628329d5SDmitry Torokhov 1831628329d5SDmitry Torokhov return 0; 1832628329d5SDmitry Torokhov } 1833628329d5SDmitry Torokhov 1834628329d5SDmitry Torokhov static int ims_pcu_identify_type(struct ims_pcu *pcu, u8 *device_id) 1835628329d5SDmitry Torokhov { 1836628329d5SDmitry Torokhov int error; 1837628329d5SDmitry Torokhov 1838628329d5SDmitry Torokhov error = ims_pcu_execute_query(pcu, GET_DEVICE_ID); 1839628329d5SDmitry Torokhov if (error) { 1840628329d5SDmitry Torokhov dev_err(pcu->dev, 1841628329d5SDmitry Torokhov "GET_DEVICE_ID command failed, error: %d\n", error); 1842628329d5SDmitry Torokhov return error; 1843628329d5SDmitry Torokhov } 1844628329d5SDmitry Torokhov 1845628329d5SDmitry Torokhov *device_id = pcu->cmd_buf[IMS_PCU_DATA_OFFSET]; 1846628329d5SDmitry Torokhov dev_dbg(pcu->dev, "Detected device ID: %d\n", *device_id); 1847628329d5SDmitry Torokhov 1848628329d5SDmitry Torokhov return 0; 1849628329d5SDmitry Torokhov } 1850628329d5SDmitry Torokhov 1851628329d5SDmitry Torokhov static int ims_pcu_init_application_mode(struct ims_pcu *pcu) 1852628329d5SDmitry Torokhov { 1853628329d5SDmitry Torokhov static atomic_t device_no = ATOMIC_INIT(0); 1854628329d5SDmitry Torokhov 1855628329d5SDmitry Torokhov const struct ims_pcu_device_info *info; 1856628329d5SDmitry Torokhov int error; 1857628329d5SDmitry Torokhov 1858628329d5SDmitry Torokhov error = ims_pcu_get_device_info(pcu); 1859628329d5SDmitry Torokhov if (error) { 1860628329d5SDmitry Torokhov /* Device does not respond to basic queries, hopeless */ 1861628329d5SDmitry Torokhov return error; 1862628329d5SDmitry Torokhov } 1863628329d5SDmitry Torokhov 1864e5fcd269SAndrey Smirnov error = ims_pcu_identify_type(pcu, &pcu->device_id); 1865628329d5SDmitry Torokhov if (error) { 1866628329d5SDmitry Torokhov dev_err(pcu->dev, 1867628329d5SDmitry Torokhov "Failed to identify device, error: %d\n", error); 1868628329d5SDmitry Torokhov /* 1869628329d5SDmitry Torokhov * Do not signal error, but do not create input nor 1870628329d5SDmitry Torokhov * backlight devices either, let userspace figure this 1871628329d5SDmitry Torokhov * out (flash a new firmware?). 1872628329d5SDmitry Torokhov */ 1873628329d5SDmitry Torokhov return 0; 1874628329d5SDmitry Torokhov } 1875628329d5SDmitry Torokhov 1876e5fcd269SAndrey Smirnov if (pcu->device_id >= ARRAY_SIZE(ims_pcu_device_info) || 1877e5fcd269SAndrey Smirnov !ims_pcu_device_info[pcu->device_id].keymap) { 1878e5fcd269SAndrey Smirnov dev_err(pcu->dev, "Device ID %d is not valid\n", pcu->device_id); 1879628329d5SDmitry Torokhov /* Same as above, punt to userspace */ 1880628329d5SDmitry Torokhov return 0; 1881628329d5SDmitry Torokhov } 1882628329d5SDmitry Torokhov 1883628329d5SDmitry Torokhov /* Device appears to be operable, complete initialization */ 1884628329d5SDmitry Torokhov pcu->device_no = atomic_inc_return(&device_no) - 1; 1885628329d5SDmitry Torokhov 1886e5fcd269SAndrey Smirnov /* 1887e5fcd269SAndrey Smirnov * PCU-B devices, both GEN_1 and GEN_2 do not have OFN sensor 1888e5fcd269SAndrey Smirnov */ 1889e5fcd269SAndrey Smirnov if (pcu->device_id != IMS_PCU_PCU_B_DEVICE_ID) { 1890e5fcd269SAndrey Smirnov error = sysfs_create_group(&pcu->dev->kobj, 1891e5fcd269SAndrey Smirnov &ims_pcu_ofn_attr_group); 1892e5fcd269SAndrey Smirnov if (error) 1893e5fcd269SAndrey Smirnov return error; 1894e5fcd269SAndrey Smirnov } 1895e5fcd269SAndrey Smirnov 1896628329d5SDmitry Torokhov error = ims_pcu_setup_backlight(pcu); 1897628329d5SDmitry Torokhov if (error) 1898628329d5SDmitry Torokhov return error; 1899628329d5SDmitry Torokhov 1900e5fcd269SAndrey Smirnov info = &ims_pcu_device_info[pcu->device_id]; 1901628329d5SDmitry Torokhov error = ims_pcu_setup_buttons(pcu, info->keymap, info->keymap_len); 1902628329d5SDmitry Torokhov if (error) 1903628329d5SDmitry Torokhov goto err_destroy_backlight; 1904628329d5SDmitry Torokhov 1905628329d5SDmitry Torokhov if (info->has_gamepad) { 1906628329d5SDmitry Torokhov error = ims_pcu_setup_gamepad(pcu); 1907628329d5SDmitry Torokhov if (error) 1908628329d5SDmitry Torokhov goto err_destroy_buttons; 1909628329d5SDmitry Torokhov } 1910628329d5SDmitry Torokhov 1911628329d5SDmitry Torokhov pcu->setup_complete = true; 1912628329d5SDmitry Torokhov 1913628329d5SDmitry Torokhov return 0; 1914628329d5SDmitry Torokhov 1915628329d5SDmitry Torokhov err_destroy_buttons: 1916628329d5SDmitry Torokhov ims_pcu_destroy_buttons(pcu); 1917ad7647d9SDmitry Torokhov err_destroy_backlight: 1918ad7647d9SDmitry Torokhov ims_pcu_destroy_backlight(pcu); 1919628329d5SDmitry Torokhov return error; 1920628329d5SDmitry Torokhov } 1921628329d5SDmitry Torokhov 1922628329d5SDmitry Torokhov static void ims_pcu_destroy_application_mode(struct ims_pcu *pcu) 1923628329d5SDmitry Torokhov { 1924628329d5SDmitry Torokhov if (pcu->setup_complete) { 1925628329d5SDmitry Torokhov pcu->setup_complete = false; 1926628329d5SDmitry Torokhov mb(); /* make sure flag setting is not reordered */ 1927628329d5SDmitry Torokhov 1928628329d5SDmitry Torokhov if (pcu->gamepad) 1929628329d5SDmitry Torokhov ims_pcu_destroy_gamepad(pcu); 1930628329d5SDmitry Torokhov ims_pcu_destroy_buttons(pcu); 1931628329d5SDmitry Torokhov ims_pcu_destroy_backlight(pcu); 1932e5fcd269SAndrey Smirnov 1933e5fcd269SAndrey Smirnov if (pcu->device_id != IMS_PCU_PCU_B_DEVICE_ID) 1934e5fcd269SAndrey Smirnov sysfs_remove_group(&pcu->dev->kobj, 1935e5fcd269SAndrey Smirnov &ims_pcu_ofn_attr_group); 1936628329d5SDmitry Torokhov } 1937628329d5SDmitry Torokhov } 1938628329d5SDmitry Torokhov 1939628329d5SDmitry Torokhov static int ims_pcu_init_bootloader_mode(struct ims_pcu *pcu) 1940628329d5SDmitry Torokhov { 1941628329d5SDmitry Torokhov int error; 1942628329d5SDmitry Torokhov 1943628329d5SDmitry Torokhov error = ims_pcu_execute_bl_command(pcu, QUERY_DEVICE, NULL, 0, 1944628329d5SDmitry Torokhov IMS_PCU_CMD_RESPONSE_TIMEOUT); 1945628329d5SDmitry Torokhov if (error) { 1946628329d5SDmitry Torokhov dev_err(pcu->dev, "Bootloader does not respond, aborting\n"); 1947628329d5SDmitry Torokhov return error; 1948628329d5SDmitry Torokhov } 1949628329d5SDmitry Torokhov 1950628329d5SDmitry Torokhov pcu->fw_start_addr = 1951628329d5SDmitry Torokhov get_unaligned_le32(&pcu->cmd_buf[IMS_PCU_DATA_OFFSET + 11]); 1952628329d5SDmitry Torokhov pcu->fw_end_addr = 1953628329d5SDmitry Torokhov get_unaligned_le32(&pcu->cmd_buf[IMS_PCU_DATA_OFFSET + 15]); 1954628329d5SDmitry Torokhov 1955628329d5SDmitry Torokhov dev_info(pcu->dev, 1956628329d5SDmitry Torokhov "Device is in bootloader mode (addr 0x%08x-0x%08x), requesting firmware\n", 1957628329d5SDmitry Torokhov pcu->fw_start_addr, pcu->fw_end_addr); 1958628329d5SDmitry Torokhov 1959628329d5SDmitry Torokhov error = request_firmware_nowait(THIS_MODULE, true, 1960628329d5SDmitry Torokhov IMS_PCU_FIRMWARE_NAME, 1961628329d5SDmitry Torokhov pcu->dev, GFP_KERNEL, pcu, 1962628329d5SDmitry Torokhov ims_pcu_process_async_firmware); 1963628329d5SDmitry Torokhov if (error) { 1964628329d5SDmitry Torokhov /* This error is not fatal, let userspace have another chance */ 1965628329d5SDmitry Torokhov complete(&pcu->async_firmware_done); 1966628329d5SDmitry Torokhov } 1967628329d5SDmitry Torokhov 1968628329d5SDmitry Torokhov return 0; 1969628329d5SDmitry Torokhov } 1970628329d5SDmitry Torokhov 1971628329d5SDmitry Torokhov static void ims_pcu_destroy_bootloader_mode(struct ims_pcu *pcu) 1972628329d5SDmitry Torokhov { 1973628329d5SDmitry Torokhov /* Make sure our initial firmware request has completed */ 1974628329d5SDmitry Torokhov wait_for_completion(&pcu->async_firmware_done); 1975628329d5SDmitry Torokhov } 1976628329d5SDmitry Torokhov 1977628329d5SDmitry Torokhov #define IMS_PCU_APPLICATION_MODE 0 1978628329d5SDmitry Torokhov #define IMS_PCU_BOOTLOADER_MODE 1 1979628329d5SDmitry Torokhov 1980628329d5SDmitry Torokhov static struct usb_driver ims_pcu_driver; 1981628329d5SDmitry Torokhov 1982628329d5SDmitry Torokhov static int ims_pcu_probe(struct usb_interface *intf, 1983628329d5SDmitry Torokhov const struct usb_device_id *id) 1984628329d5SDmitry Torokhov { 1985628329d5SDmitry Torokhov struct usb_device *udev = interface_to_usbdev(intf); 1986628329d5SDmitry Torokhov struct ims_pcu *pcu; 1987628329d5SDmitry Torokhov int error; 1988628329d5SDmitry Torokhov 1989628329d5SDmitry Torokhov pcu = kzalloc(sizeof(struct ims_pcu), GFP_KERNEL); 1990628329d5SDmitry Torokhov if (!pcu) 1991628329d5SDmitry Torokhov return -ENOMEM; 1992628329d5SDmitry Torokhov 1993628329d5SDmitry Torokhov pcu->dev = &intf->dev; 1994628329d5SDmitry Torokhov pcu->udev = udev; 1995628329d5SDmitry Torokhov pcu->bootloader_mode = id->driver_info == IMS_PCU_BOOTLOADER_MODE; 1996628329d5SDmitry Torokhov mutex_init(&pcu->cmd_mutex); 1997628329d5SDmitry Torokhov init_completion(&pcu->cmd_done); 1998628329d5SDmitry Torokhov init_completion(&pcu->async_firmware_done); 1999628329d5SDmitry Torokhov 2000628329d5SDmitry Torokhov error = ims_pcu_parse_cdc_data(intf, pcu); 2001628329d5SDmitry Torokhov if (error) 2002628329d5SDmitry Torokhov goto err_free_mem; 2003628329d5SDmitry Torokhov 2004628329d5SDmitry Torokhov error = usb_driver_claim_interface(&ims_pcu_driver, 2005628329d5SDmitry Torokhov pcu->data_intf, pcu); 2006628329d5SDmitry Torokhov if (error) { 2007628329d5SDmitry Torokhov dev_err(&intf->dev, 2008628329d5SDmitry Torokhov "Unable to claim corresponding data interface: %d\n", 2009628329d5SDmitry Torokhov error); 2010628329d5SDmitry Torokhov goto err_free_mem; 2011628329d5SDmitry Torokhov } 2012628329d5SDmitry Torokhov 2013628329d5SDmitry Torokhov usb_set_intfdata(pcu->ctrl_intf, pcu); 2014628329d5SDmitry Torokhov usb_set_intfdata(pcu->data_intf, pcu); 2015628329d5SDmitry Torokhov 2016628329d5SDmitry Torokhov error = ims_pcu_buffers_alloc(pcu); 2017628329d5SDmitry Torokhov if (error) 2018628329d5SDmitry Torokhov goto err_unclaim_intf; 2019628329d5SDmitry Torokhov 2020628329d5SDmitry Torokhov error = ims_pcu_start_io(pcu); 2021628329d5SDmitry Torokhov if (error) 2022628329d5SDmitry Torokhov goto err_free_buffers; 2023628329d5SDmitry Torokhov 2024628329d5SDmitry Torokhov error = ims_pcu_line_setup(pcu); 2025628329d5SDmitry Torokhov if (error) 2026628329d5SDmitry Torokhov goto err_stop_io; 2027628329d5SDmitry Torokhov 2028628329d5SDmitry Torokhov error = sysfs_create_group(&intf->dev.kobj, &ims_pcu_attr_group); 2029628329d5SDmitry Torokhov if (error) 2030628329d5SDmitry Torokhov goto err_stop_io; 2031628329d5SDmitry Torokhov 2032628329d5SDmitry Torokhov error = pcu->bootloader_mode ? 2033628329d5SDmitry Torokhov ims_pcu_init_bootloader_mode(pcu) : 2034628329d5SDmitry Torokhov ims_pcu_init_application_mode(pcu); 2035628329d5SDmitry Torokhov if (error) 2036628329d5SDmitry Torokhov goto err_remove_sysfs; 2037628329d5SDmitry Torokhov 2038628329d5SDmitry Torokhov return 0; 2039628329d5SDmitry Torokhov 2040628329d5SDmitry Torokhov err_remove_sysfs: 2041628329d5SDmitry Torokhov sysfs_remove_group(&intf->dev.kobj, &ims_pcu_attr_group); 2042628329d5SDmitry Torokhov err_stop_io: 2043628329d5SDmitry Torokhov ims_pcu_stop_io(pcu); 2044628329d5SDmitry Torokhov err_free_buffers: 2045628329d5SDmitry Torokhov ims_pcu_buffers_free(pcu); 2046628329d5SDmitry Torokhov err_unclaim_intf: 2047628329d5SDmitry Torokhov usb_driver_release_interface(&ims_pcu_driver, pcu->data_intf); 2048628329d5SDmitry Torokhov err_free_mem: 2049628329d5SDmitry Torokhov kfree(pcu); 2050628329d5SDmitry Torokhov return error; 2051628329d5SDmitry Torokhov } 2052628329d5SDmitry Torokhov 2053628329d5SDmitry Torokhov static void ims_pcu_disconnect(struct usb_interface *intf) 2054628329d5SDmitry Torokhov { 2055628329d5SDmitry Torokhov struct ims_pcu *pcu = usb_get_intfdata(intf); 2056628329d5SDmitry Torokhov struct usb_host_interface *alt = intf->cur_altsetting; 2057628329d5SDmitry Torokhov 2058628329d5SDmitry Torokhov usb_set_intfdata(intf, NULL); 2059628329d5SDmitry Torokhov 2060628329d5SDmitry Torokhov /* 2061628329d5SDmitry Torokhov * See if we are dealing with control or data interface. The cleanup 2062628329d5SDmitry Torokhov * happens when we unbind primary (control) interface. 2063628329d5SDmitry Torokhov */ 2064628329d5SDmitry Torokhov if (alt->desc.bInterfaceClass != USB_CLASS_COMM) 2065628329d5SDmitry Torokhov return; 2066628329d5SDmitry Torokhov 2067628329d5SDmitry Torokhov sysfs_remove_group(&intf->dev.kobj, &ims_pcu_attr_group); 2068628329d5SDmitry Torokhov 2069628329d5SDmitry Torokhov ims_pcu_stop_io(pcu); 2070628329d5SDmitry Torokhov 2071628329d5SDmitry Torokhov if (pcu->bootloader_mode) 2072628329d5SDmitry Torokhov ims_pcu_destroy_bootloader_mode(pcu); 2073628329d5SDmitry Torokhov else 2074628329d5SDmitry Torokhov ims_pcu_destroy_application_mode(pcu); 2075628329d5SDmitry Torokhov 2076628329d5SDmitry Torokhov ims_pcu_buffers_free(pcu); 2077628329d5SDmitry Torokhov kfree(pcu); 2078628329d5SDmitry Torokhov } 2079628329d5SDmitry Torokhov 2080628329d5SDmitry Torokhov #ifdef CONFIG_PM 2081628329d5SDmitry Torokhov static int ims_pcu_suspend(struct usb_interface *intf, 2082628329d5SDmitry Torokhov pm_message_t message) 2083628329d5SDmitry Torokhov { 2084628329d5SDmitry Torokhov struct ims_pcu *pcu = usb_get_intfdata(intf); 2085628329d5SDmitry Torokhov struct usb_host_interface *alt = intf->cur_altsetting; 2086628329d5SDmitry Torokhov 2087628329d5SDmitry Torokhov if (alt->desc.bInterfaceClass == USB_CLASS_COMM) 2088628329d5SDmitry Torokhov ims_pcu_stop_io(pcu); 2089628329d5SDmitry Torokhov 2090628329d5SDmitry Torokhov return 0; 2091628329d5SDmitry Torokhov } 2092628329d5SDmitry Torokhov 2093628329d5SDmitry Torokhov static int ims_pcu_resume(struct usb_interface *intf) 2094628329d5SDmitry Torokhov { 2095628329d5SDmitry Torokhov struct ims_pcu *pcu = usb_get_intfdata(intf); 2096628329d5SDmitry Torokhov struct usb_host_interface *alt = intf->cur_altsetting; 2097628329d5SDmitry Torokhov int retval = 0; 2098628329d5SDmitry Torokhov 2099628329d5SDmitry Torokhov if (alt->desc.bInterfaceClass == USB_CLASS_COMM) { 2100628329d5SDmitry Torokhov retval = ims_pcu_start_io(pcu); 2101628329d5SDmitry Torokhov if (retval == 0) 2102628329d5SDmitry Torokhov retval = ims_pcu_line_setup(pcu); 2103628329d5SDmitry Torokhov } 2104628329d5SDmitry Torokhov 2105628329d5SDmitry Torokhov return retval; 2106628329d5SDmitry Torokhov } 2107628329d5SDmitry Torokhov #endif 2108628329d5SDmitry Torokhov 2109628329d5SDmitry Torokhov static const struct usb_device_id ims_pcu_id_table[] = { 2110628329d5SDmitry Torokhov { 2111628329d5SDmitry Torokhov USB_DEVICE_AND_INTERFACE_INFO(0x04d8, 0x0082, 2112628329d5SDmitry Torokhov USB_CLASS_COMM, 2113628329d5SDmitry Torokhov USB_CDC_SUBCLASS_ACM, 2114628329d5SDmitry Torokhov USB_CDC_ACM_PROTO_AT_V25TER), 2115628329d5SDmitry Torokhov .driver_info = IMS_PCU_APPLICATION_MODE, 2116628329d5SDmitry Torokhov }, 2117628329d5SDmitry Torokhov { 2118628329d5SDmitry Torokhov USB_DEVICE_AND_INTERFACE_INFO(0x04d8, 0x0083, 2119628329d5SDmitry Torokhov USB_CLASS_COMM, 2120628329d5SDmitry Torokhov USB_CDC_SUBCLASS_ACM, 2121628329d5SDmitry Torokhov USB_CDC_ACM_PROTO_AT_V25TER), 2122628329d5SDmitry Torokhov .driver_info = IMS_PCU_BOOTLOADER_MODE, 2123628329d5SDmitry Torokhov }, 2124628329d5SDmitry Torokhov { } 2125628329d5SDmitry Torokhov }; 2126628329d5SDmitry Torokhov 2127628329d5SDmitry Torokhov static struct usb_driver ims_pcu_driver = { 2128628329d5SDmitry Torokhov .name = "ims_pcu", 2129628329d5SDmitry Torokhov .id_table = ims_pcu_id_table, 2130628329d5SDmitry Torokhov .probe = ims_pcu_probe, 2131628329d5SDmitry Torokhov .disconnect = ims_pcu_disconnect, 2132628329d5SDmitry Torokhov #ifdef CONFIG_PM 2133628329d5SDmitry Torokhov .suspend = ims_pcu_suspend, 2134628329d5SDmitry Torokhov .resume = ims_pcu_resume, 2135628329d5SDmitry Torokhov .reset_resume = ims_pcu_resume, 2136628329d5SDmitry Torokhov #endif 2137628329d5SDmitry Torokhov }; 2138628329d5SDmitry Torokhov 2139628329d5SDmitry Torokhov module_usb_driver(ims_pcu_driver); 2140628329d5SDmitry Torokhov 2141628329d5SDmitry Torokhov MODULE_DESCRIPTION("IMS Passenger Control Unit driver"); 2142628329d5SDmitry Torokhov MODULE_AUTHOR("Dmitry Torokhov <dmitry.torokhov@gmail.com>"); 2143628329d5SDmitry Torokhov MODULE_LICENSE("GPL"); 2144