1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164 |
- /*
- * Driver for IMS Passenger Control Unit Devices
- *
- * Copyright (C) 2013 The IMS Company
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License version 2
- * as published by the Free Software Foundation.
- */
- #include <linux/completion.h>
- #include <linux/device.h>
- #include <linux/firmware.h>
- #include <linux/ihex.h>
- #include <linux/input.h>
- #include <linux/kernel.h>
- #include <linux/leds.h>
- #include <linux/module.h>
- #include <linux/slab.h>
- #include <linux/types.h>
- #include <linux/usb/input.h>
- #include <linux/usb/cdc.h>
- #include <asm/unaligned.h>
- #define IMS_PCU_KEYMAP_LEN 32
- struct ims_pcu_buttons {
- struct input_dev *input;
- char name[32];
- char phys[32];
- unsigned short keymap[IMS_PCU_KEYMAP_LEN];
- };
- struct ims_pcu_gamepad {
- struct input_dev *input;
- char name[32];
- char phys[32];
- };
- struct ims_pcu_backlight {
- struct led_classdev cdev;
- struct work_struct work;
- enum led_brightness desired_brightness;
- char name[32];
- };
- #define IMS_PCU_PART_NUMBER_LEN 15
- #define IMS_PCU_SERIAL_NUMBER_LEN 8
- #define IMS_PCU_DOM_LEN 8
- #define IMS_PCU_FW_VERSION_LEN (9 + 1)
- #define IMS_PCU_BL_VERSION_LEN (9 + 1)
- #define IMS_PCU_BL_RESET_REASON_LEN (2 + 1)
- #define IMS_PCU_PCU_B_DEVICE_ID 5
- #define IMS_PCU_BUF_SIZE 128
- struct ims_pcu {
- struct usb_device *udev;
- struct device *dev; /* control interface's device, used for logging */
- unsigned int device_no;
- bool bootloader_mode;
- char part_number[IMS_PCU_PART_NUMBER_LEN];
- char serial_number[IMS_PCU_SERIAL_NUMBER_LEN];
- char date_of_manufacturing[IMS_PCU_DOM_LEN];
- char fw_version[IMS_PCU_FW_VERSION_LEN];
- char bl_version[IMS_PCU_BL_VERSION_LEN];
- char reset_reason[IMS_PCU_BL_RESET_REASON_LEN];
- int update_firmware_status;
- u8 device_id;
- u8 ofn_reg_addr;
- struct usb_interface *ctrl_intf;
- struct usb_endpoint_descriptor *ep_ctrl;
- struct urb *urb_ctrl;
- u8 *urb_ctrl_buf;
- dma_addr_t ctrl_dma;
- size_t max_ctrl_size;
- struct usb_interface *data_intf;
- struct usb_endpoint_descriptor *ep_in;
- struct urb *urb_in;
- u8 *urb_in_buf;
- dma_addr_t read_dma;
- size_t max_in_size;
- struct usb_endpoint_descriptor *ep_out;
- u8 *urb_out_buf;
- size_t max_out_size;
- u8 read_buf[IMS_PCU_BUF_SIZE];
- u8 read_pos;
- u8 check_sum;
- bool have_stx;
- bool have_dle;
- u8 cmd_buf[IMS_PCU_BUF_SIZE];
- u8 ack_id;
- u8 expected_response;
- u8 cmd_buf_len;
- struct completion cmd_done;
- struct mutex cmd_mutex;
- u32 fw_start_addr;
- u32 fw_end_addr;
- struct completion async_firmware_done;
- struct ims_pcu_buttons buttons;
- struct ims_pcu_gamepad *gamepad;
- struct ims_pcu_backlight backlight;
- bool setup_complete; /* Input and LED devices have been created */
- };
- /*********************************************************************
- * Buttons Input device support *
- *********************************************************************/
- static const unsigned short ims_pcu_keymap_1[] = {
- [1] = KEY_ATTENDANT_OFF,
- [2] = KEY_ATTENDANT_ON,
- [3] = KEY_LIGHTS_TOGGLE,
- [4] = KEY_VOLUMEUP,
- [5] = KEY_VOLUMEDOWN,
- [6] = KEY_INFO,
- };
- static const unsigned short ims_pcu_keymap_2[] = {
- [4] = KEY_VOLUMEUP,
- [5] = KEY_VOLUMEDOWN,
- [6] = KEY_INFO,
- };
- static const unsigned short ims_pcu_keymap_3[] = {
- [1] = KEY_HOMEPAGE,
- [2] = KEY_ATTENDANT_TOGGLE,
- [3] = KEY_LIGHTS_TOGGLE,
- [4] = KEY_VOLUMEUP,
- [5] = KEY_VOLUMEDOWN,
- [6] = KEY_DISPLAYTOGGLE,
- [18] = KEY_PLAYPAUSE,
- };
- static const unsigned short ims_pcu_keymap_4[] = {
- [1] = KEY_ATTENDANT_OFF,
- [2] = KEY_ATTENDANT_ON,
- [3] = KEY_LIGHTS_TOGGLE,
- [4] = KEY_VOLUMEUP,
- [5] = KEY_VOLUMEDOWN,
- [6] = KEY_INFO,
- [18] = KEY_PLAYPAUSE,
- };
- static const unsigned short ims_pcu_keymap_5[] = {
- [1] = KEY_ATTENDANT_OFF,
- [2] = KEY_ATTENDANT_ON,
- [3] = KEY_LIGHTS_TOGGLE,
- };
- struct ims_pcu_device_info {
- const unsigned short *keymap;
- size_t keymap_len;
- bool has_gamepad;
- };
- #define IMS_PCU_DEVINFO(_n, _gamepad) \
- [_n] = { \
- .keymap = ims_pcu_keymap_##_n, \
- .keymap_len = ARRAY_SIZE(ims_pcu_keymap_##_n), \
- .has_gamepad = _gamepad, \
- }
- static const struct ims_pcu_device_info ims_pcu_device_info[] = {
- IMS_PCU_DEVINFO(1, true),
- IMS_PCU_DEVINFO(2, true),
- IMS_PCU_DEVINFO(3, true),
- IMS_PCU_DEVINFO(4, true),
- IMS_PCU_DEVINFO(5, false),
- };
- static void ims_pcu_buttons_report(struct ims_pcu *pcu, u32 data)
- {
- struct ims_pcu_buttons *buttons = &pcu->buttons;
- struct input_dev *input = buttons->input;
- int i;
- for (i = 0; i < 32; i++) {
- unsigned short keycode = buttons->keymap[i];
- if (keycode != KEY_RESERVED)
- input_report_key(input, keycode, data & (1UL << i));
- }
- input_sync(input);
- }
- static int ims_pcu_setup_buttons(struct ims_pcu *pcu,
- const unsigned short *keymap,
- size_t keymap_len)
- {
- struct ims_pcu_buttons *buttons = &pcu->buttons;
- struct input_dev *input;
- int i;
- int error;
- input = input_allocate_device();
- if (!input) {
- dev_err(pcu->dev,
- "Not enough memory for input input device\n");
- return -ENOMEM;
- }
- snprintf(buttons->name, sizeof(buttons->name),
- "IMS PCU#%d Button Interface", pcu->device_no);
- usb_make_path(pcu->udev, buttons->phys, sizeof(buttons->phys));
- strlcat(buttons->phys, "/input0", sizeof(buttons->phys));
- memcpy(buttons->keymap, keymap, sizeof(*keymap) * keymap_len);
- input->name = buttons->name;
- input->phys = buttons->phys;
- usb_to_input_id(pcu->udev, &input->id);
- input->dev.parent = &pcu->ctrl_intf->dev;
- input->keycode = buttons->keymap;
- input->keycodemax = ARRAY_SIZE(buttons->keymap);
- input->keycodesize = sizeof(buttons->keymap[0]);
- __set_bit(EV_KEY, input->evbit);
- for (i = 0; i < IMS_PCU_KEYMAP_LEN; i++)
- __set_bit(buttons->keymap[i], input->keybit);
- __clear_bit(KEY_RESERVED, input->keybit);
- error = input_register_device(input);
- if (error) {
- dev_err(pcu->dev,
- "Failed to register buttons input device: %d\n",
- error);
- input_free_device(input);
- return error;
- }
- buttons->input = input;
- return 0;
- }
- static void ims_pcu_destroy_buttons(struct ims_pcu *pcu)
- {
- struct ims_pcu_buttons *buttons = &pcu->buttons;
- input_unregister_device(buttons->input);
- }
- /*********************************************************************
- * Gamepad Input device support *
- *********************************************************************/
- static void ims_pcu_gamepad_report(struct ims_pcu *pcu, u32 data)
- {
- struct ims_pcu_gamepad *gamepad = pcu->gamepad;
- struct input_dev *input = gamepad->input;
- int x, y;
- x = !!(data & (1 << 14)) - !!(data & (1 << 13));
- y = !!(data & (1 << 12)) - !!(data & (1 << 11));
- input_report_abs(input, ABS_X, x);
- input_report_abs(input, ABS_Y, y);
- input_report_key(input, BTN_A, data & (1 << 7));
- input_report_key(input, BTN_B, data & (1 << 8));
- input_report_key(input, BTN_X, data & (1 << 9));
- input_report_key(input, BTN_Y, data & (1 << 10));
- input_report_key(input, BTN_START, data & (1 << 15));
- input_report_key(input, BTN_SELECT, data & (1 << 16));
- input_sync(input);
- }
- static int ims_pcu_setup_gamepad(struct ims_pcu *pcu)
- {
- struct ims_pcu_gamepad *gamepad;
- struct input_dev *input;
- int error;
- gamepad = kzalloc(sizeof(struct ims_pcu_gamepad), GFP_KERNEL);
- input = input_allocate_device();
- if (!gamepad || !input) {
- dev_err(pcu->dev,
- "Not enough memory for gamepad device\n");
- error = -ENOMEM;
- goto err_free_mem;
- }
- gamepad->input = input;
- snprintf(gamepad->name, sizeof(gamepad->name),
- "IMS PCU#%d Gamepad Interface", pcu->device_no);
- usb_make_path(pcu->udev, gamepad->phys, sizeof(gamepad->phys));
- strlcat(gamepad->phys, "/input1", sizeof(gamepad->phys));
- input->name = gamepad->name;
- input->phys = gamepad->phys;
- usb_to_input_id(pcu->udev, &input->id);
- input->dev.parent = &pcu->ctrl_intf->dev;
- __set_bit(EV_KEY, input->evbit);
- __set_bit(BTN_A, input->keybit);
- __set_bit(BTN_B, input->keybit);
- __set_bit(BTN_X, input->keybit);
- __set_bit(BTN_Y, input->keybit);
- __set_bit(BTN_START, input->keybit);
- __set_bit(BTN_SELECT, input->keybit);
- __set_bit(EV_ABS, input->evbit);
- input_set_abs_params(input, ABS_X, -1, 1, 0, 0);
- input_set_abs_params(input, ABS_Y, -1, 1, 0, 0);
- error = input_register_device(input);
- if (error) {
- dev_err(pcu->dev,
- "Failed to register gamepad input device: %d\n",
- error);
- goto err_free_mem;
- }
- pcu->gamepad = gamepad;
- return 0;
- err_free_mem:
- input_free_device(input);
- kfree(gamepad);
- return -ENOMEM;
- }
- static void ims_pcu_destroy_gamepad(struct ims_pcu *pcu)
- {
- struct ims_pcu_gamepad *gamepad = pcu->gamepad;
- input_unregister_device(gamepad->input);
- kfree(gamepad);
- }
- /*********************************************************************
- * PCU Communication protocol handling *
- *********************************************************************/
- #define IMS_PCU_PROTOCOL_STX 0x02
- #define IMS_PCU_PROTOCOL_ETX 0x03
- #define IMS_PCU_PROTOCOL_DLE 0x10
- /* PCU commands */
- #define IMS_PCU_CMD_STATUS 0xa0
- #define IMS_PCU_CMD_PCU_RESET 0xa1
- #define IMS_PCU_CMD_RESET_REASON 0xa2
- #define IMS_PCU_CMD_SEND_BUTTONS 0xa3
- #define IMS_PCU_CMD_JUMP_TO_BTLDR 0xa4
- #define IMS_PCU_CMD_GET_INFO 0xa5
- #define IMS_PCU_CMD_SET_BRIGHTNESS 0xa6
- #define IMS_PCU_CMD_EEPROM 0xa7
- #define IMS_PCU_CMD_GET_FW_VERSION 0xa8
- #define IMS_PCU_CMD_GET_BL_VERSION 0xa9
- #define IMS_PCU_CMD_SET_INFO 0xab
- #define IMS_PCU_CMD_GET_BRIGHTNESS 0xac
- #define IMS_PCU_CMD_GET_DEVICE_ID 0xae
- #define IMS_PCU_CMD_SPECIAL_INFO 0xb0
- #define IMS_PCU_CMD_BOOTLOADER 0xb1 /* Pass data to bootloader */
- #define IMS_PCU_CMD_OFN_SET_CONFIG 0xb3
- #define IMS_PCU_CMD_OFN_GET_CONFIG 0xb4
- /* PCU responses */
- #define IMS_PCU_RSP_STATUS 0xc0
- #define IMS_PCU_RSP_PCU_RESET 0 /* Originally 0xc1 */
- #define IMS_PCU_RSP_RESET_REASON 0xc2
- #define IMS_PCU_RSP_SEND_BUTTONS 0xc3
- #define IMS_PCU_RSP_JUMP_TO_BTLDR 0 /* Originally 0xc4 */
- #define IMS_PCU_RSP_GET_INFO 0xc5
- #define IMS_PCU_RSP_SET_BRIGHTNESS 0xc6
- #define IMS_PCU_RSP_EEPROM 0xc7
- #define IMS_PCU_RSP_GET_FW_VERSION 0xc8
- #define IMS_PCU_RSP_GET_BL_VERSION 0xc9
- #define IMS_PCU_RSP_SET_INFO 0xcb
- #define IMS_PCU_RSP_GET_BRIGHTNESS 0xcc
- #define IMS_PCU_RSP_CMD_INVALID 0xcd
- #define IMS_PCU_RSP_GET_DEVICE_ID 0xce
- #define IMS_PCU_RSP_SPECIAL_INFO 0xd0
- #define IMS_PCU_RSP_BOOTLOADER 0xd1 /* Bootloader response */
- #define IMS_PCU_RSP_OFN_SET_CONFIG 0xd2
- #define IMS_PCU_RSP_OFN_GET_CONFIG 0xd3
- #define IMS_PCU_RSP_EVNT_BUTTONS 0xe0 /* Unsolicited, button state */
- #define IMS_PCU_GAMEPAD_MASK 0x0001ff80UL /* Bits 7 through 16 */
- #define IMS_PCU_MIN_PACKET_LEN 3
- #define IMS_PCU_DATA_OFFSET 2
- #define IMS_PCU_CMD_WRITE_TIMEOUT 100 /* msec */
- #define IMS_PCU_CMD_RESPONSE_TIMEOUT 500 /* msec */
- static void ims_pcu_report_events(struct ims_pcu *pcu)
- {
- u32 data = get_unaligned_be32(&pcu->read_buf[3]);
- ims_pcu_buttons_report(pcu, data & ~IMS_PCU_GAMEPAD_MASK);
- if (pcu->gamepad)
- ims_pcu_gamepad_report(pcu, data);
- }
- static void ims_pcu_handle_response(struct ims_pcu *pcu)
- {
- switch (pcu->read_buf[0]) {
- case IMS_PCU_RSP_EVNT_BUTTONS:
- if (likely(pcu->setup_complete))
- ims_pcu_report_events(pcu);
- break;
- default:
- /*
- * See if we got command completion.
- * If both the sequence and response code match save
- * the data and signal completion.
- */
- if (pcu->read_buf[0] == pcu->expected_response &&
- pcu->read_buf[1] == pcu->ack_id - 1) {
- memcpy(pcu->cmd_buf, pcu->read_buf, pcu->read_pos);
- pcu->cmd_buf_len = pcu->read_pos;
- complete(&pcu->cmd_done);
- }
- break;
- }
- }
- static void ims_pcu_process_data(struct ims_pcu *pcu, struct urb *urb)
- {
- int i;
- for (i = 0; i < urb->actual_length; i++) {
- u8 data = pcu->urb_in_buf[i];
- /* Skip everything until we get Start Xmit */
- if (!pcu->have_stx && data != IMS_PCU_PROTOCOL_STX)
- continue;
- if (pcu->have_dle) {
- pcu->have_dle = false;
- pcu->read_buf[pcu->read_pos++] = data;
- pcu->check_sum += data;
- continue;
- }
- switch (data) {
- case IMS_PCU_PROTOCOL_STX:
- if (pcu->have_stx)
- dev_warn(pcu->dev,
- "Unexpected STX at byte %d, discarding old data\n",
- pcu->read_pos);
- pcu->have_stx = true;
- pcu->have_dle = false;
- pcu->read_pos = 0;
- pcu->check_sum = 0;
- break;
- case IMS_PCU_PROTOCOL_DLE:
- pcu->have_dle = true;
- break;
- case IMS_PCU_PROTOCOL_ETX:
- if (pcu->read_pos < IMS_PCU_MIN_PACKET_LEN) {
- dev_warn(pcu->dev,
- "Short packet received (%d bytes), ignoring\n",
- pcu->read_pos);
- } else if (pcu->check_sum != 0) {
- dev_warn(pcu->dev,
- "Invalid checksum in packet (%d bytes), ignoring\n",
- pcu->read_pos);
- } else {
- ims_pcu_handle_response(pcu);
- }
- pcu->have_stx = false;
- pcu->have_dle = false;
- pcu->read_pos = 0;
- break;
- default:
- pcu->read_buf[pcu->read_pos++] = data;
- pcu->check_sum += data;
- break;
- }
- }
- }
- static bool ims_pcu_byte_needs_escape(u8 byte)
- {
- return byte == IMS_PCU_PROTOCOL_STX ||
- byte == IMS_PCU_PROTOCOL_ETX ||
- byte == IMS_PCU_PROTOCOL_DLE;
- }
- static int ims_pcu_send_cmd_chunk(struct ims_pcu *pcu,
- u8 command, int chunk, int len)
- {
- int error;
- error = usb_bulk_msg(pcu->udev,
- usb_sndbulkpipe(pcu->udev,
- pcu->ep_out->bEndpointAddress),
- pcu->urb_out_buf, len,
- NULL, IMS_PCU_CMD_WRITE_TIMEOUT);
- if (error < 0) {
- dev_dbg(pcu->dev,
- "Sending 0x%02x command failed at chunk %d: %d\n",
- command, chunk, error);
- return error;
- }
- return 0;
- }
- static int ims_pcu_send_command(struct ims_pcu *pcu,
- u8 command, const u8 *data, int len)
- {
- int count = 0;
- int chunk = 0;
- int delta;
- int i;
- int error;
- u8 csum = 0;
- u8 ack_id;
- pcu->urb_out_buf[count++] = IMS_PCU_PROTOCOL_STX;
- /* We know the command need not be escaped */
- pcu->urb_out_buf[count++] = command;
- csum += command;
- ack_id = pcu->ack_id++;
- if (ack_id == 0xff)
- ack_id = pcu->ack_id++;
- if (ims_pcu_byte_needs_escape(ack_id))
- pcu->urb_out_buf[count++] = IMS_PCU_PROTOCOL_DLE;
- pcu->urb_out_buf[count++] = ack_id;
- csum += ack_id;
- for (i = 0; i < len; i++) {
- delta = ims_pcu_byte_needs_escape(data[i]) ? 2 : 1;
- if (count + delta >= pcu->max_out_size) {
- error = ims_pcu_send_cmd_chunk(pcu, command,
- ++chunk, count);
- if (error)
- return error;
- count = 0;
- }
- if (delta == 2)
- pcu->urb_out_buf[count++] = IMS_PCU_PROTOCOL_DLE;
- pcu->urb_out_buf[count++] = data[i];
- csum += data[i];
- }
- csum = 1 + ~csum;
- delta = ims_pcu_byte_needs_escape(csum) ? 3 : 2;
- if (count + delta >= pcu->max_out_size) {
- error = ims_pcu_send_cmd_chunk(pcu, command, ++chunk, count);
- if (error)
- return error;
- count = 0;
- }
- if (delta == 3)
- pcu->urb_out_buf[count++] = IMS_PCU_PROTOCOL_DLE;
- pcu->urb_out_buf[count++] = csum;
- pcu->urb_out_buf[count++] = IMS_PCU_PROTOCOL_ETX;
- return ims_pcu_send_cmd_chunk(pcu, command, ++chunk, count);
- }
- static int __ims_pcu_execute_command(struct ims_pcu *pcu,
- u8 command, const void *data, size_t len,
- u8 expected_response, int response_time)
- {
- int error;
- pcu->expected_response = expected_response;
- init_completion(&pcu->cmd_done);
- error = ims_pcu_send_command(pcu, command, data, len);
- if (error)
- return error;
- if (expected_response &&
- !wait_for_completion_timeout(&pcu->cmd_done,
- msecs_to_jiffies(response_time))) {
- dev_dbg(pcu->dev, "Command 0x%02x timed out\n", command);
- return -ETIMEDOUT;
- }
- return 0;
- }
- #define ims_pcu_execute_command(pcu, code, data, len) \
- __ims_pcu_execute_command(pcu, \
- IMS_PCU_CMD_##code, data, len, \
- IMS_PCU_RSP_##code, \
- IMS_PCU_CMD_RESPONSE_TIMEOUT)
- #define ims_pcu_execute_query(pcu, code) \
- ims_pcu_execute_command(pcu, code, NULL, 0)
- /* Bootloader commands */
- #define IMS_PCU_BL_CMD_QUERY_DEVICE 0xa1
- #define IMS_PCU_BL_CMD_UNLOCK_CONFIG 0xa2
- #define IMS_PCU_BL_CMD_ERASE_APP 0xa3
- #define IMS_PCU_BL_CMD_PROGRAM_DEVICE 0xa4
- #define IMS_PCU_BL_CMD_PROGRAM_COMPLETE 0xa5
- #define IMS_PCU_BL_CMD_READ_APP 0xa6
- #define IMS_PCU_BL_CMD_RESET_DEVICE 0xa7
- #define IMS_PCU_BL_CMD_LAUNCH_APP 0xa8
- /* Bootloader commands */
- #define IMS_PCU_BL_RSP_QUERY_DEVICE 0xc1
- #define IMS_PCU_BL_RSP_UNLOCK_CONFIG 0xc2
- #define IMS_PCU_BL_RSP_ERASE_APP 0xc3
- #define IMS_PCU_BL_RSP_PROGRAM_DEVICE 0xc4
- #define IMS_PCU_BL_RSP_PROGRAM_COMPLETE 0xc5
- #define IMS_PCU_BL_RSP_READ_APP 0xc6
- #define IMS_PCU_BL_RSP_RESET_DEVICE 0 /* originally 0xa7 */
- #define IMS_PCU_BL_RSP_LAUNCH_APP 0 /* originally 0xa8 */
- #define IMS_PCU_BL_DATA_OFFSET 3
- static int __ims_pcu_execute_bl_command(struct ims_pcu *pcu,
- u8 command, const void *data, size_t len,
- u8 expected_response, int response_time)
- {
- int error;
- pcu->cmd_buf[0] = command;
- if (data)
- memcpy(&pcu->cmd_buf[1], data, len);
- error = __ims_pcu_execute_command(pcu,
- IMS_PCU_CMD_BOOTLOADER, pcu->cmd_buf, len + 1,
- expected_response ? IMS_PCU_RSP_BOOTLOADER : 0,
- response_time);
- if (error) {
- dev_err(pcu->dev,
- "Failure when sending 0x%02x command to bootloader, error: %d\n",
- pcu->cmd_buf[0], error);
- return error;
- }
- if (expected_response && pcu->cmd_buf[2] != expected_response) {
- dev_err(pcu->dev,
- "Unexpected response from bootloader: 0x%02x, wanted 0x%02x\n",
- pcu->cmd_buf[2], expected_response);
- return -EINVAL;
- }
- return 0;
- }
- #define ims_pcu_execute_bl_command(pcu, code, data, len, timeout) \
- __ims_pcu_execute_bl_command(pcu, \
- IMS_PCU_BL_CMD_##code, data, len, \
- IMS_PCU_BL_RSP_##code, timeout) \
- #define IMS_PCU_INFO_PART_OFFSET 2
- #define IMS_PCU_INFO_DOM_OFFSET 17
- #define IMS_PCU_INFO_SERIAL_OFFSET 25
- #define IMS_PCU_SET_INFO_SIZE 31
- static int ims_pcu_get_info(struct ims_pcu *pcu)
- {
- int error;
- error = ims_pcu_execute_query(pcu, GET_INFO);
- if (error) {
- dev_err(pcu->dev,
- "GET_INFO command failed, error: %d\n", error);
- return error;
- }
- memcpy(pcu->part_number,
- &pcu->cmd_buf[IMS_PCU_INFO_PART_OFFSET],
- sizeof(pcu->part_number));
- memcpy(pcu->date_of_manufacturing,
- &pcu->cmd_buf[IMS_PCU_INFO_DOM_OFFSET],
- sizeof(pcu->date_of_manufacturing));
- memcpy(pcu->serial_number,
- &pcu->cmd_buf[IMS_PCU_INFO_SERIAL_OFFSET],
- sizeof(pcu->serial_number));
- return 0;
- }
- static int ims_pcu_set_info(struct ims_pcu *pcu)
- {
- int error;
- memcpy(&pcu->cmd_buf[IMS_PCU_INFO_PART_OFFSET],
- pcu->part_number, sizeof(pcu->part_number));
- memcpy(&pcu->cmd_buf[IMS_PCU_INFO_DOM_OFFSET],
- pcu->date_of_manufacturing, sizeof(pcu->date_of_manufacturing));
- memcpy(&pcu->cmd_buf[IMS_PCU_INFO_SERIAL_OFFSET],
- pcu->serial_number, sizeof(pcu->serial_number));
- error = ims_pcu_execute_command(pcu, SET_INFO,
- &pcu->cmd_buf[IMS_PCU_DATA_OFFSET],
- IMS_PCU_SET_INFO_SIZE);
- if (error) {
- dev_err(pcu->dev,
- "Failed to update device information, error: %d\n",
- error);
- return error;
- }
- return 0;
- }
- static int ims_pcu_switch_to_bootloader(struct ims_pcu *pcu)
- {
- int error;
- /* Execute jump to the bootoloader */
- error = ims_pcu_execute_command(pcu, JUMP_TO_BTLDR, NULL, 0);
- if (error) {
- dev_err(pcu->dev,
- "Failure when sending JUMP TO BOOLTLOADER command, error: %d\n",
- error);
- return error;
- }
- return 0;
- }
- /*********************************************************************
- * Firmware Update handling *
- *********************************************************************/
- #define IMS_PCU_FIRMWARE_NAME "imspcu.fw"
- struct ims_pcu_flash_fmt {
- __le32 addr;
- u8 len;
- u8 data[];
- };
- static unsigned int ims_pcu_count_fw_records(const struct firmware *fw)
- {
- const struct ihex_binrec *rec = (const struct ihex_binrec *)fw->data;
- unsigned int count = 0;
- while (rec) {
- count++;
- rec = ihex_next_binrec(rec);
- }
- return count;
- }
- static int ims_pcu_verify_block(struct ims_pcu *pcu,
- u32 addr, u8 len, const u8 *data)
- {
- struct ims_pcu_flash_fmt *fragment;
- int error;
- fragment = (void *)&pcu->cmd_buf[1];
- put_unaligned_le32(addr, &fragment->addr);
- fragment->len = len;
- error = ims_pcu_execute_bl_command(pcu, READ_APP, NULL, 5,
- IMS_PCU_CMD_RESPONSE_TIMEOUT);
- if (error) {
- dev_err(pcu->dev,
- "Failed to retrieve block at 0x%08x, len %d, error: %d\n",
- addr, len, error);
- return error;
- }
- fragment = (void *)&pcu->cmd_buf[IMS_PCU_BL_DATA_OFFSET];
- if (get_unaligned_le32(&fragment->addr) != addr ||
- fragment->len != len) {
- dev_err(pcu->dev,
- "Wrong block when retrieving 0x%08x (0x%08x), len %d (%d)\n",
- addr, get_unaligned_le32(&fragment->addr),
- len, fragment->len);
- return -EINVAL;
- }
- if (memcmp(fragment->data, data, len)) {
- dev_err(pcu->dev,
- "Mismatch in block at 0x%08x, len %d\n",
- addr, len);
- return -EINVAL;
- }
- return 0;
- }
- static int ims_pcu_flash_firmware(struct ims_pcu *pcu,
- const struct firmware *fw,
- unsigned int n_fw_records)
- {
- const struct ihex_binrec *rec = (const struct ihex_binrec *)fw->data;
- struct ims_pcu_flash_fmt *fragment;
- unsigned int count = 0;
- u32 addr;
- u8 len;
- int error;
- error = ims_pcu_execute_bl_command(pcu, ERASE_APP, NULL, 0, 2000);
- if (error) {
- dev_err(pcu->dev,
- "Failed to erase application image, error: %d\n",
- error);
- return error;
- }
- while (rec) {
- /*
- * The firmware format is messed up for some reason.
- * The address twice that of what is needed for some
- * reason and we end up overwriting half of the data
- * with the next record.
- */
- addr = be32_to_cpu(rec->addr) / 2;
- len = be16_to_cpu(rec->len);
- fragment = (void *)&pcu->cmd_buf[1];
- put_unaligned_le32(addr, &fragment->addr);
- fragment->len = len;
- memcpy(fragment->data, rec->data, len);
- error = ims_pcu_execute_bl_command(pcu, PROGRAM_DEVICE,
- NULL, len + 5,
- IMS_PCU_CMD_RESPONSE_TIMEOUT);
- if (error) {
- dev_err(pcu->dev,
- "Failed to write block at 0x%08x, len %d, error: %d\n",
- addr, len, error);
- return error;
- }
- if (addr >= pcu->fw_start_addr && addr < pcu->fw_end_addr) {
- error = ims_pcu_verify_block(pcu, addr, len, rec->data);
- if (error)
- return error;
- }
- count++;
- pcu->update_firmware_status = (count * 100) / n_fw_records;
- rec = ihex_next_binrec(rec);
- }
- error = ims_pcu_execute_bl_command(pcu, PROGRAM_COMPLETE,
- NULL, 0, 2000);
- if (error)
- dev_err(pcu->dev,
- "Failed to send PROGRAM_COMPLETE, error: %d\n",
- error);
- return 0;
- }
- static int ims_pcu_handle_firmware_update(struct ims_pcu *pcu,
- const struct firmware *fw)
- {
- unsigned int n_fw_records;
- int retval;
- dev_info(pcu->dev, "Updating firmware %s, size: %zu\n",
- IMS_PCU_FIRMWARE_NAME, fw->size);
- n_fw_records = ims_pcu_count_fw_records(fw);
- retval = ims_pcu_flash_firmware(pcu, fw, n_fw_records);
- if (retval)
- goto out;
- retval = ims_pcu_execute_bl_command(pcu, LAUNCH_APP, NULL, 0, 0);
- if (retval)
- dev_err(pcu->dev,
- "Failed to start application image, error: %d\n",
- retval);
- out:
- pcu->update_firmware_status = retval;
- sysfs_notify(&pcu->dev->kobj, NULL, "update_firmware_status");
- return retval;
- }
- static void ims_pcu_process_async_firmware(const struct firmware *fw,
- void *context)
- {
- struct ims_pcu *pcu = context;
- int error;
- if (!fw) {
- dev_err(pcu->dev, "Failed to get firmware %s\n",
- IMS_PCU_FIRMWARE_NAME);
- goto out;
- }
- error = ihex_validate_fw(fw);
- if (error) {
- dev_err(pcu->dev, "Firmware %s is invalid\n",
- IMS_PCU_FIRMWARE_NAME);
- goto out;
- }
- mutex_lock(&pcu->cmd_mutex);
- ims_pcu_handle_firmware_update(pcu, fw);
- mutex_unlock(&pcu->cmd_mutex);
- release_firmware(fw);
- out:
- complete(&pcu->async_firmware_done);
- }
- /*********************************************************************
- * Backlight LED device support *
- *********************************************************************/
- #define IMS_PCU_MAX_BRIGHTNESS 31998
- static void ims_pcu_backlight_work(struct work_struct *work)
- {
- struct ims_pcu_backlight *backlight =
- container_of(work, struct ims_pcu_backlight, work);
- struct ims_pcu *pcu =
- container_of(backlight, struct ims_pcu, backlight);
- int desired_brightness = backlight->desired_brightness;
- __le16 br_val = cpu_to_le16(desired_brightness);
- int error;
- mutex_lock(&pcu->cmd_mutex);
- error = ims_pcu_execute_command(pcu, SET_BRIGHTNESS,
- &br_val, sizeof(br_val));
- if (error && error != -ENODEV)
- dev_warn(pcu->dev,
- "Failed to set desired brightness %u, error: %d\n",
- desired_brightness, error);
- mutex_unlock(&pcu->cmd_mutex);
- }
- static void ims_pcu_backlight_set_brightness(struct led_classdev *cdev,
- enum led_brightness value)
- {
- struct ims_pcu_backlight *backlight =
- container_of(cdev, struct ims_pcu_backlight, cdev);
- backlight->desired_brightness = value;
- schedule_work(&backlight->work);
- }
- static enum led_brightness
- ims_pcu_backlight_get_brightness(struct led_classdev *cdev)
- {
- struct ims_pcu_backlight *backlight =
- container_of(cdev, struct ims_pcu_backlight, cdev);
- struct ims_pcu *pcu =
- container_of(backlight, struct ims_pcu, backlight);
- int brightness;
- int error;
- mutex_lock(&pcu->cmd_mutex);
- error = ims_pcu_execute_query(pcu, GET_BRIGHTNESS);
- if (error) {
- dev_warn(pcu->dev,
- "Failed to get current brightness, error: %d\n",
- error);
- /* Assume the LED is OFF */
- brightness = LED_OFF;
- } else {
- brightness =
- get_unaligned_le16(&pcu->cmd_buf[IMS_PCU_DATA_OFFSET]);
- }
- mutex_unlock(&pcu->cmd_mutex);
- return brightness;
- }
- static int ims_pcu_setup_backlight(struct ims_pcu *pcu)
- {
- struct ims_pcu_backlight *backlight = &pcu->backlight;
- int error;
- INIT_WORK(&backlight->work, ims_pcu_backlight_work);
- snprintf(backlight->name, sizeof(backlight->name),
- "pcu%d::kbd_backlight", pcu->device_no);
- backlight->cdev.name = backlight->name;
- backlight->cdev.max_brightness = IMS_PCU_MAX_BRIGHTNESS;
- backlight->cdev.brightness_get = ims_pcu_backlight_get_brightness;
- backlight->cdev.brightness_set = ims_pcu_backlight_set_brightness;
- error = led_classdev_register(pcu->dev, &backlight->cdev);
- if (error) {
- dev_err(pcu->dev,
- "Failed to register backlight LED device, error: %d\n",
- error);
- return error;
- }
- return 0;
- }
- static void ims_pcu_destroy_backlight(struct ims_pcu *pcu)
- {
- struct ims_pcu_backlight *backlight = &pcu->backlight;
- led_classdev_unregister(&backlight->cdev);
- cancel_work_sync(&backlight->work);
- }
- /*********************************************************************
- * Sysfs attributes handling *
- *********************************************************************/
- struct ims_pcu_attribute {
- struct device_attribute dattr;
- size_t field_offset;
- int field_length;
- };
- static ssize_t ims_pcu_attribute_show(struct device *dev,
- struct device_attribute *dattr,
- char *buf)
- {
- struct usb_interface *intf = to_usb_interface(dev);
- struct ims_pcu *pcu = usb_get_intfdata(intf);
- struct ims_pcu_attribute *attr =
- container_of(dattr, struct ims_pcu_attribute, dattr);
- char *field = (char *)pcu + attr->field_offset;
- return scnprintf(buf, PAGE_SIZE, "%.*s\n", attr->field_length, field);
- }
- static ssize_t ims_pcu_attribute_store(struct device *dev,
- struct device_attribute *dattr,
- const char *buf, size_t count)
- {
- struct usb_interface *intf = to_usb_interface(dev);
- struct ims_pcu *pcu = usb_get_intfdata(intf);
- struct ims_pcu_attribute *attr =
- container_of(dattr, struct ims_pcu_attribute, dattr);
- char *field = (char *)pcu + attr->field_offset;
- size_t data_len;
- int error;
- if (count > attr->field_length)
- return -EINVAL;
- data_len = strnlen(buf, attr->field_length);
- if (data_len > attr->field_length)
- return -EINVAL;
- error = mutex_lock_interruptible(&pcu->cmd_mutex);
- if (error)
- return error;
- memset(field, 0, attr->field_length);
- memcpy(field, buf, data_len);
- error = ims_pcu_set_info(pcu);
- /*
- * Even if update failed, let's fetch the info again as we just
- * clobbered one of the fields.
- */
- ims_pcu_get_info(pcu);
- mutex_unlock(&pcu->cmd_mutex);
- return error < 0 ? error : count;
- }
- #define IMS_PCU_ATTR(_field, _mode) \
- struct ims_pcu_attribute ims_pcu_attr_##_field = { \
- .dattr = __ATTR(_field, _mode, \
- ims_pcu_attribute_show, \
- ims_pcu_attribute_store), \
- .field_offset = offsetof(struct ims_pcu, _field), \
- .field_length = sizeof(((struct ims_pcu *)NULL)->_field), \
- }
- #define IMS_PCU_RO_ATTR(_field) \
- IMS_PCU_ATTR(_field, S_IRUGO)
- #define IMS_PCU_RW_ATTR(_field) \
- IMS_PCU_ATTR(_field, S_IRUGO | S_IWUSR)
- static IMS_PCU_RW_ATTR(part_number);
- static IMS_PCU_RW_ATTR(serial_number);
- static IMS_PCU_RW_ATTR(date_of_manufacturing);
- static IMS_PCU_RO_ATTR(fw_version);
- static IMS_PCU_RO_ATTR(bl_version);
- static IMS_PCU_RO_ATTR(reset_reason);
- static ssize_t ims_pcu_reset_device(struct device *dev,
- struct device_attribute *dattr,
- const char *buf, size_t count)
- {
- static const u8 reset_byte = 1;
- struct usb_interface *intf = to_usb_interface(dev);
- struct ims_pcu *pcu = usb_get_intfdata(intf);
- int value;
- int error;
- error = kstrtoint(buf, 0, &value);
- if (error)
- return error;
- if (value != 1)
- return -EINVAL;
- dev_info(pcu->dev, "Attempting to reset device\n");
- error = ims_pcu_execute_command(pcu, PCU_RESET, &reset_byte, 1);
- if (error) {
- dev_info(pcu->dev,
- "Failed to reset device, error: %d\n",
- error);
- return error;
- }
- return count;
- }
- static DEVICE_ATTR(reset_device, S_IWUSR, NULL, ims_pcu_reset_device);
- static ssize_t ims_pcu_update_firmware_store(struct device *dev,
- struct device_attribute *dattr,
- const char *buf, size_t count)
- {
- struct usb_interface *intf = to_usb_interface(dev);
- struct ims_pcu *pcu = usb_get_intfdata(intf);
- const struct firmware *fw = NULL;
- int value;
- int error;
- error = kstrtoint(buf, 0, &value);
- if (error)
- return error;
- if (value != 1)
- return -EINVAL;
- error = mutex_lock_interruptible(&pcu->cmd_mutex);
- if (error)
- return error;
- error = request_ihex_firmware(&fw, IMS_PCU_FIRMWARE_NAME, pcu->dev);
- if (error) {
- dev_err(pcu->dev, "Failed to request firmware %s, error: %d\n",
- IMS_PCU_FIRMWARE_NAME, error);
- goto out;
- }
- /*
- * If we are already in bootloader mode we can proceed with
- * flashing the firmware.
- *
- * If we are in application mode, then we need to switch into
- * bootloader mode, which will cause the device to disconnect
- * and reconnect as different device.
- */
- if (pcu->bootloader_mode)
- error = ims_pcu_handle_firmware_update(pcu, fw);
- else
- error = ims_pcu_switch_to_bootloader(pcu);
- release_firmware(fw);
- out:
- mutex_unlock(&pcu->cmd_mutex);
- return error ?: count;
- }
- static DEVICE_ATTR(update_firmware, S_IWUSR,
- NULL, ims_pcu_update_firmware_store);
- static ssize_t
- ims_pcu_update_firmware_status_show(struct device *dev,
- struct device_attribute *dattr,
- char *buf)
- {
- struct usb_interface *intf = to_usb_interface(dev);
- struct ims_pcu *pcu = usb_get_intfdata(intf);
- return scnprintf(buf, PAGE_SIZE, "%d\n", pcu->update_firmware_status);
- }
- static DEVICE_ATTR(update_firmware_status, S_IRUGO,
- ims_pcu_update_firmware_status_show, NULL);
- static struct attribute *ims_pcu_attrs[] = {
- &ims_pcu_attr_part_number.dattr.attr,
- &ims_pcu_attr_serial_number.dattr.attr,
- &ims_pcu_attr_date_of_manufacturing.dattr.attr,
- &ims_pcu_attr_fw_version.dattr.attr,
- &ims_pcu_attr_bl_version.dattr.attr,
- &ims_pcu_attr_reset_reason.dattr.attr,
- &dev_attr_reset_device.attr,
- &dev_attr_update_firmware.attr,
- &dev_attr_update_firmware_status.attr,
- NULL
- };
- static umode_t ims_pcu_is_attr_visible(struct kobject *kobj,
- struct attribute *attr, int n)
- {
- struct device *dev = container_of(kobj, struct device, kobj);
- struct usb_interface *intf = to_usb_interface(dev);
- struct ims_pcu *pcu = usb_get_intfdata(intf);
- umode_t mode = attr->mode;
- if (pcu->bootloader_mode) {
- if (attr != &dev_attr_update_firmware_status.attr &&
- attr != &dev_attr_update_firmware.attr &&
- attr != &dev_attr_reset_device.attr) {
- mode = 0;
- }
- } else {
- if (attr == &dev_attr_update_firmware_status.attr)
- mode = 0;
- }
- return mode;
- }
- static struct attribute_group ims_pcu_attr_group = {
- .is_visible = ims_pcu_is_attr_visible,
- .attrs = ims_pcu_attrs,
- };
- /* Support for a separate OFN attribute group */
- #define OFN_REG_RESULT_OFFSET 2
- static int ims_pcu_read_ofn_config(struct ims_pcu *pcu, u8 addr, u8 *data)
- {
- int error;
- s16 result;
- error = ims_pcu_execute_command(pcu, OFN_GET_CONFIG,
- &addr, sizeof(addr));
- if (error)
- return error;
- result = (s16)get_unaligned_le16(pcu->cmd_buf + OFN_REG_RESULT_OFFSET);
- if (result < 0)
- return -EIO;
- /* We only need LSB */
- *data = pcu->cmd_buf[OFN_REG_RESULT_OFFSET];
- return 0;
- }
- static int ims_pcu_write_ofn_config(struct ims_pcu *pcu, u8 addr, u8 data)
- {
- u8 buffer[] = { addr, data };
- int error;
- s16 result;
- error = ims_pcu_execute_command(pcu, OFN_SET_CONFIG,
- &buffer, sizeof(buffer));
- if (error)
- return error;
- result = (s16)get_unaligned_le16(pcu->cmd_buf + OFN_REG_RESULT_OFFSET);
- if (result < 0)
- return -EIO;
- return 0;
- }
- static ssize_t ims_pcu_ofn_reg_data_show(struct device *dev,
- struct device_attribute *dattr,
- char *buf)
- {
- struct usb_interface *intf = to_usb_interface(dev);
- struct ims_pcu *pcu = usb_get_intfdata(intf);
- int error;
- u8 data;
- mutex_lock(&pcu->cmd_mutex);
- error = ims_pcu_read_ofn_config(pcu, pcu->ofn_reg_addr, &data);
- mutex_unlock(&pcu->cmd_mutex);
- if (error)
- return error;
- return scnprintf(buf, PAGE_SIZE, "%x\n", data);
- }
- static ssize_t ims_pcu_ofn_reg_data_store(struct device *dev,
- struct device_attribute *dattr,
- const char *buf, size_t count)
- {
- struct usb_interface *intf = to_usb_interface(dev);
- struct ims_pcu *pcu = usb_get_intfdata(intf);
- int error;
- u8 value;
- error = kstrtou8(buf, 0, &value);
- if (error)
- return error;
- mutex_lock(&pcu->cmd_mutex);
- error = ims_pcu_write_ofn_config(pcu, pcu->ofn_reg_addr, value);
- mutex_unlock(&pcu->cmd_mutex);
- return error ?: count;
- }
- static DEVICE_ATTR(reg_data, S_IRUGO | S_IWUSR,
- ims_pcu_ofn_reg_data_show, ims_pcu_ofn_reg_data_store);
- static ssize_t ims_pcu_ofn_reg_addr_show(struct device *dev,
- struct device_attribute *dattr,
- char *buf)
- {
- struct usb_interface *intf = to_usb_interface(dev);
- struct ims_pcu *pcu = usb_get_intfdata(intf);
- int error;
- mutex_lock(&pcu->cmd_mutex);
- error = scnprintf(buf, PAGE_SIZE, "%x\n", pcu->ofn_reg_addr);
- mutex_unlock(&pcu->cmd_mutex);
- return error;
- }
- static ssize_t ims_pcu_ofn_reg_addr_store(struct device *dev,
- struct device_attribute *dattr,
- const char *buf, size_t count)
- {
- struct usb_interface *intf = to_usb_interface(dev);
- struct ims_pcu *pcu = usb_get_intfdata(intf);
- int error;
- u8 value;
- error = kstrtou8(buf, 0, &value);
- if (error)
- return error;
- mutex_lock(&pcu->cmd_mutex);
- pcu->ofn_reg_addr = value;
- mutex_unlock(&pcu->cmd_mutex);
- return count;
- }
- static DEVICE_ATTR(reg_addr, S_IRUGO | S_IWUSR,
- ims_pcu_ofn_reg_addr_show, ims_pcu_ofn_reg_addr_store);
- struct ims_pcu_ofn_bit_attribute {
- struct device_attribute dattr;
- u8 addr;
- u8 nr;
- };
- static ssize_t ims_pcu_ofn_bit_show(struct device *dev,
- struct device_attribute *dattr,
- char *buf)
- {
- struct usb_interface *intf = to_usb_interface(dev);
- struct ims_pcu *pcu = usb_get_intfdata(intf);
- struct ims_pcu_ofn_bit_attribute *attr =
- container_of(dattr, struct ims_pcu_ofn_bit_attribute, dattr);
- int error;
- u8 data;
- mutex_lock(&pcu->cmd_mutex);
- error = ims_pcu_read_ofn_config(pcu, attr->addr, &data);
- mutex_unlock(&pcu->cmd_mutex);
- if (error)
- return error;
- return scnprintf(buf, PAGE_SIZE, "%d\n", !!(data & (1 << attr->nr)));
- }
- static ssize_t ims_pcu_ofn_bit_store(struct device *dev,
- struct device_attribute *dattr,
- const char *buf, size_t count)
- {
- struct usb_interface *intf = to_usb_interface(dev);
- struct ims_pcu *pcu = usb_get_intfdata(intf);
- struct ims_pcu_ofn_bit_attribute *attr =
- container_of(dattr, struct ims_pcu_ofn_bit_attribute, dattr);
- int error;
- int value;
- u8 data;
- error = kstrtoint(buf, 0, &value);
- if (error)
- return error;
- if (value > 1)
- return -EINVAL;
- mutex_lock(&pcu->cmd_mutex);
- error = ims_pcu_read_ofn_config(pcu, attr->addr, &data);
- if (!error) {
- if (value)
- data |= 1U << attr->nr;
- else
- data &= ~(1U << attr->nr);
- error = ims_pcu_write_ofn_config(pcu, attr->addr, data);
- }
- mutex_unlock(&pcu->cmd_mutex);
- return error ?: count;
- }
- #define IMS_PCU_OFN_BIT_ATTR(_field, _addr, _nr) \
- struct ims_pcu_ofn_bit_attribute ims_pcu_ofn_attr_##_field = { \
- .dattr = __ATTR(_field, S_IWUSR | S_IRUGO, \
- ims_pcu_ofn_bit_show, ims_pcu_ofn_bit_store), \
- .addr = _addr, \
- .nr = _nr, \
- }
- static IMS_PCU_OFN_BIT_ATTR(engine_enable, 0x60, 7);
- static IMS_PCU_OFN_BIT_ATTR(speed_enable, 0x60, 6);
- static IMS_PCU_OFN_BIT_ATTR(assert_enable, 0x60, 5);
- static IMS_PCU_OFN_BIT_ATTR(xyquant_enable, 0x60, 4);
- static IMS_PCU_OFN_BIT_ATTR(xyscale_enable, 0x60, 1);
- static IMS_PCU_OFN_BIT_ATTR(scale_x2, 0x63, 6);
- static IMS_PCU_OFN_BIT_ATTR(scale_y2, 0x63, 7);
- static struct attribute *ims_pcu_ofn_attrs[] = {
- &dev_attr_reg_data.attr,
- &dev_attr_reg_addr.attr,
- &ims_pcu_ofn_attr_engine_enable.dattr.attr,
- &ims_pcu_ofn_attr_speed_enable.dattr.attr,
- &ims_pcu_ofn_attr_assert_enable.dattr.attr,
- &ims_pcu_ofn_attr_xyquant_enable.dattr.attr,
- &ims_pcu_ofn_attr_xyscale_enable.dattr.attr,
- &ims_pcu_ofn_attr_scale_x2.dattr.attr,
- &ims_pcu_ofn_attr_scale_y2.dattr.attr,
- NULL
- };
- static struct attribute_group ims_pcu_ofn_attr_group = {
- .name = "ofn",
- .attrs = ims_pcu_ofn_attrs,
- };
- static void ims_pcu_irq(struct urb *urb)
- {
- struct ims_pcu *pcu = urb->context;
- int retval, status;
- status = urb->status;
- switch (status) {
- case 0:
- /* success */
- break;
- case -ECONNRESET:
- case -ENOENT:
- case -ESHUTDOWN:
- /* this urb is terminated, clean up */
- dev_dbg(pcu->dev, "%s - urb shutting down with status: %d\n",
- __func__, status);
- return;
- default:
- dev_dbg(pcu->dev, "%s - nonzero urb status received: %d\n",
- __func__, status);
- goto exit;
- }
- dev_dbg(pcu->dev, "%s: received %d: %*ph\n", __func__,
- urb->actual_length, urb->actual_length, pcu->urb_in_buf);
- if (urb == pcu->urb_in)
- ims_pcu_process_data(pcu, urb);
- exit:
- retval = usb_submit_urb(urb, GFP_ATOMIC);
- if (retval && retval != -ENODEV)
- dev_err(pcu->dev, "%s - usb_submit_urb failed with result %d\n",
- __func__, retval);
- }
- static int ims_pcu_buffers_alloc(struct ims_pcu *pcu)
- {
- int error;
- pcu->urb_in_buf = usb_alloc_coherent(pcu->udev, pcu->max_in_size,
- GFP_KERNEL, &pcu->read_dma);
- if (!pcu->urb_in_buf) {
- dev_err(pcu->dev,
- "Failed to allocate memory for read buffer\n");
- return -ENOMEM;
- }
- pcu->urb_in = usb_alloc_urb(0, GFP_KERNEL);
- if (!pcu->urb_in) {
- dev_err(pcu->dev, "Failed to allocate input URB\n");
- error = -ENOMEM;
- goto err_free_urb_in_buf;
- }
- pcu->urb_in->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
- pcu->urb_in->transfer_dma = pcu->read_dma;
- usb_fill_bulk_urb(pcu->urb_in, pcu->udev,
- usb_rcvbulkpipe(pcu->udev,
- pcu->ep_in->bEndpointAddress),
- pcu->urb_in_buf, pcu->max_in_size,
- ims_pcu_irq, pcu);
- /*
- * We are using usb_bulk_msg() for sending so there is no point
- * in allocating memory with usb_alloc_coherent().
- */
- pcu->urb_out_buf = kmalloc(pcu->max_out_size, GFP_KERNEL);
- if (!pcu->urb_out_buf) {
- dev_err(pcu->dev, "Failed to allocate memory for write buffer\n");
- error = -ENOMEM;
- goto err_free_in_urb;
- }
- pcu->urb_ctrl_buf = usb_alloc_coherent(pcu->udev, pcu->max_ctrl_size,
- GFP_KERNEL, &pcu->ctrl_dma);
- if (!pcu->urb_ctrl_buf) {
- dev_err(pcu->dev,
- "Failed to allocate memory for read buffer\n");
- error = -ENOMEM;
- goto err_free_urb_out_buf;
- }
- pcu->urb_ctrl = usb_alloc_urb(0, GFP_KERNEL);
- if (!pcu->urb_ctrl) {
- dev_err(pcu->dev, "Failed to allocate input URB\n");
- error = -ENOMEM;
- goto err_free_urb_ctrl_buf;
- }
- pcu->urb_ctrl->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
- pcu->urb_ctrl->transfer_dma = pcu->ctrl_dma;
- usb_fill_int_urb(pcu->urb_ctrl, pcu->udev,
- usb_rcvintpipe(pcu->udev,
- pcu->ep_ctrl->bEndpointAddress),
- pcu->urb_ctrl_buf, pcu->max_ctrl_size,
- ims_pcu_irq, pcu, pcu->ep_ctrl->bInterval);
- return 0;
- err_free_urb_ctrl_buf:
- usb_free_coherent(pcu->udev, pcu->max_ctrl_size,
- pcu->urb_ctrl_buf, pcu->ctrl_dma);
- err_free_urb_out_buf:
- kfree(pcu->urb_out_buf);
- err_free_in_urb:
- usb_free_urb(pcu->urb_in);
- err_free_urb_in_buf:
- usb_free_coherent(pcu->udev, pcu->max_in_size,
- pcu->urb_in_buf, pcu->read_dma);
- return error;
- }
- static void ims_pcu_buffers_free(struct ims_pcu *pcu)
- {
- usb_kill_urb(pcu->urb_in);
- usb_free_urb(pcu->urb_in);
- usb_free_coherent(pcu->udev, pcu->max_out_size,
- pcu->urb_in_buf, pcu->read_dma);
- kfree(pcu->urb_out_buf);
- usb_kill_urb(pcu->urb_ctrl);
- usb_free_urb(pcu->urb_ctrl);
- usb_free_coherent(pcu->udev, pcu->max_ctrl_size,
- pcu->urb_ctrl_buf, pcu->ctrl_dma);
- }
- static const struct usb_cdc_union_desc *
- ims_pcu_get_cdc_union_desc(struct usb_interface *intf)
- {
- const void *buf = intf->altsetting->extra;
- size_t buflen = intf->altsetting->extralen;
- struct usb_cdc_union_desc *union_desc;
- if (!buf) {
- dev_err(&intf->dev, "Missing descriptor data\n");
- return NULL;
- }
- if (!buflen) {
- dev_err(&intf->dev, "Zero length descriptor\n");
- return NULL;
- }
- while (buflen >= sizeof(*union_desc)) {
- union_desc = (struct usb_cdc_union_desc *)buf;
- if (union_desc->bLength > buflen) {
- dev_err(&intf->dev, "Too large descriptor\n");
- return NULL;
- }
- if (union_desc->bDescriptorType == USB_DT_CS_INTERFACE &&
- union_desc->bDescriptorSubType == USB_CDC_UNION_TYPE) {
- dev_dbg(&intf->dev, "Found union header\n");
- if (union_desc->bLength >= sizeof(*union_desc))
- return union_desc;
- dev_err(&intf->dev,
- "Union descriptor to short (%d vs %zd\n)",
- union_desc->bLength, sizeof(*union_desc));
- return NULL;
- }
- buflen -= union_desc->bLength;
- buf += union_desc->bLength;
- }
- dev_err(&intf->dev, "Missing CDC union descriptor\n");
- return NULL;
- }
- static int ims_pcu_parse_cdc_data(struct usb_interface *intf, struct ims_pcu *pcu)
- {
- const struct usb_cdc_union_desc *union_desc;
- struct usb_host_interface *alt;
- union_desc = ims_pcu_get_cdc_union_desc(intf);
- if (!union_desc)
- return -EINVAL;
- pcu->ctrl_intf = usb_ifnum_to_if(pcu->udev,
- union_desc->bMasterInterface0);
- if (!pcu->ctrl_intf)
- return -EINVAL;
- alt = pcu->ctrl_intf->cur_altsetting;
- if (alt->desc.bNumEndpoints < 1)
- return -ENODEV;
- pcu->ep_ctrl = &alt->endpoint[0].desc;
- pcu->max_ctrl_size = usb_endpoint_maxp(pcu->ep_ctrl);
- pcu->data_intf = usb_ifnum_to_if(pcu->udev,
- union_desc->bSlaveInterface0);
- if (!pcu->data_intf)
- return -EINVAL;
- alt = pcu->data_intf->cur_altsetting;
- if (alt->desc.bNumEndpoints != 2) {
- dev_err(pcu->dev,
- "Incorrect number of endpoints on data interface (%d)\n",
- alt->desc.bNumEndpoints);
- return -EINVAL;
- }
- pcu->ep_out = &alt->endpoint[0].desc;
- if (!usb_endpoint_is_bulk_out(pcu->ep_out)) {
- dev_err(pcu->dev,
- "First endpoint on data interface is not BULK OUT\n");
- return -EINVAL;
- }
- pcu->max_out_size = usb_endpoint_maxp(pcu->ep_out);
- if (pcu->max_out_size < 8) {
- dev_err(pcu->dev,
- "Max OUT packet size is too small (%zd)\n",
- pcu->max_out_size);
- return -EINVAL;
- }
- pcu->ep_in = &alt->endpoint[1].desc;
- if (!usb_endpoint_is_bulk_in(pcu->ep_in)) {
- dev_err(pcu->dev,
- "Second endpoint on data interface is not BULK IN\n");
- return -EINVAL;
- }
- pcu->max_in_size = usb_endpoint_maxp(pcu->ep_in);
- if (pcu->max_in_size < 8) {
- dev_err(pcu->dev,
- "Max IN packet size is too small (%zd)\n",
- pcu->max_in_size);
- return -EINVAL;
- }
- return 0;
- }
- static int ims_pcu_start_io(struct ims_pcu *pcu)
- {
- int error;
- error = usb_submit_urb(pcu->urb_ctrl, GFP_KERNEL);
- if (error) {
- dev_err(pcu->dev,
- "Failed to start control IO - usb_submit_urb failed with result: %d\n",
- error);
- return -EIO;
- }
- error = usb_submit_urb(pcu->urb_in, GFP_KERNEL);
- if (error) {
- dev_err(pcu->dev,
- "Failed to start IO - usb_submit_urb failed with result: %d\n",
- error);
- usb_kill_urb(pcu->urb_ctrl);
- return -EIO;
- }
- return 0;
- }
- static void ims_pcu_stop_io(struct ims_pcu *pcu)
- {
- usb_kill_urb(pcu->urb_in);
- usb_kill_urb(pcu->urb_ctrl);
- }
- static int ims_pcu_line_setup(struct ims_pcu *pcu)
- {
- struct usb_host_interface *interface = pcu->ctrl_intf->cur_altsetting;
- struct usb_cdc_line_coding *line = (void *)pcu->cmd_buf;
- int error;
- memset(line, 0, sizeof(*line));
- line->dwDTERate = cpu_to_le32(57600);
- line->bDataBits = 8;
- error = usb_control_msg(pcu->udev, usb_sndctrlpipe(pcu->udev, 0),
- USB_CDC_REQ_SET_LINE_CODING,
- USB_TYPE_CLASS | USB_RECIP_INTERFACE,
- 0, interface->desc.bInterfaceNumber,
- line, sizeof(struct usb_cdc_line_coding),
- 5000);
- if (error < 0) {
- dev_err(pcu->dev, "Failed to set line coding, error: %d\n",
- error);
- return error;
- }
- error = usb_control_msg(pcu->udev, usb_sndctrlpipe(pcu->udev, 0),
- USB_CDC_REQ_SET_CONTROL_LINE_STATE,
- USB_TYPE_CLASS | USB_RECIP_INTERFACE,
- 0x03, interface->desc.bInterfaceNumber,
- NULL, 0, 5000);
- if (error < 0) {
- dev_err(pcu->dev, "Failed to set line state, error: %d\n",
- error);
- return error;
- }
- return 0;
- }
- static int ims_pcu_get_device_info(struct ims_pcu *pcu)
- {
- int error;
- error = ims_pcu_get_info(pcu);
- if (error)
- return error;
- error = ims_pcu_execute_query(pcu, GET_FW_VERSION);
- if (error) {
- dev_err(pcu->dev,
- "GET_FW_VERSION command failed, error: %d\n", error);
- return error;
- }
- snprintf(pcu->fw_version, sizeof(pcu->fw_version),
- "%02d%02d%02d%02d.%c%c",
- pcu->cmd_buf[2], pcu->cmd_buf[3], pcu->cmd_buf[4], pcu->cmd_buf[5],
- pcu->cmd_buf[6], pcu->cmd_buf[7]);
- error = ims_pcu_execute_query(pcu, GET_BL_VERSION);
- if (error) {
- dev_err(pcu->dev,
- "GET_BL_VERSION command failed, error: %d\n", error);
- return error;
- }
- snprintf(pcu->bl_version, sizeof(pcu->bl_version),
- "%02d%02d%02d%02d.%c%c",
- pcu->cmd_buf[2], pcu->cmd_buf[3], pcu->cmd_buf[4], pcu->cmd_buf[5],
- pcu->cmd_buf[6], pcu->cmd_buf[7]);
- error = ims_pcu_execute_query(pcu, RESET_REASON);
- if (error) {
- dev_err(pcu->dev,
- "RESET_REASON command failed, error: %d\n", error);
- return error;
- }
- snprintf(pcu->reset_reason, sizeof(pcu->reset_reason),
- "%02x", pcu->cmd_buf[IMS_PCU_DATA_OFFSET]);
- dev_dbg(pcu->dev,
- "P/N: %s, MD: %s, S/N: %s, FW: %s, BL: %s, RR: %s\n",
- pcu->part_number,
- pcu->date_of_manufacturing,
- pcu->serial_number,
- pcu->fw_version,
- pcu->bl_version,
- pcu->reset_reason);
- return 0;
- }
- static int ims_pcu_identify_type(struct ims_pcu *pcu, u8 *device_id)
- {
- int error;
- error = ims_pcu_execute_query(pcu, GET_DEVICE_ID);
- if (error) {
- dev_err(pcu->dev,
- "GET_DEVICE_ID command failed, error: %d\n", error);
- return error;
- }
- *device_id = pcu->cmd_buf[IMS_PCU_DATA_OFFSET];
- dev_dbg(pcu->dev, "Detected device ID: %d\n", *device_id);
- return 0;
- }
- static int ims_pcu_init_application_mode(struct ims_pcu *pcu)
- {
- static atomic_t device_no = ATOMIC_INIT(-1);
- const struct ims_pcu_device_info *info;
- int error;
- error = ims_pcu_get_device_info(pcu);
- if (error) {
- /* Device does not respond to basic queries, hopeless */
- return error;
- }
- error = ims_pcu_identify_type(pcu, &pcu->device_id);
- if (error) {
- dev_err(pcu->dev,
- "Failed to identify device, error: %d\n", error);
- /*
- * Do not signal error, but do not create input nor
- * backlight devices either, let userspace figure this
- * out (flash a new firmware?).
- */
- return 0;
- }
- if (pcu->device_id >= ARRAY_SIZE(ims_pcu_device_info) ||
- !ims_pcu_device_info[pcu->device_id].keymap) {
- dev_err(pcu->dev, "Device ID %d is not valid\n", pcu->device_id);
- /* Same as above, punt to userspace */
- return 0;
- }
- /* Device appears to be operable, complete initialization */
- pcu->device_no = atomic_inc_return(&device_no);
- /*
- * PCU-B devices, both GEN_1 and GEN_2 do not have OFN sensor
- */
- if (pcu->device_id != IMS_PCU_PCU_B_DEVICE_ID) {
- error = sysfs_create_group(&pcu->dev->kobj,
- &ims_pcu_ofn_attr_group);
- if (error)
- return error;
- }
- error = ims_pcu_setup_backlight(pcu);
- if (error)
- return error;
- info = &ims_pcu_device_info[pcu->device_id];
- error = ims_pcu_setup_buttons(pcu, info->keymap, info->keymap_len);
- if (error)
- goto err_destroy_backlight;
- if (info->has_gamepad) {
- error = ims_pcu_setup_gamepad(pcu);
- if (error)
- goto err_destroy_buttons;
- }
- pcu->setup_complete = true;
- return 0;
- err_destroy_buttons:
- ims_pcu_destroy_buttons(pcu);
- err_destroy_backlight:
- ims_pcu_destroy_backlight(pcu);
- return error;
- }
- static void ims_pcu_destroy_application_mode(struct ims_pcu *pcu)
- {
- if (pcu->setup_complete) {
- pcu->setup_complete = false;
- mb(); /* make sure flag setting is not reordered */
- if (pcu->gamepad)
- ims_pcu_destroy_gamepad(pcu);
- ims_pcu_destroy_buttons(pcu);
- ims_pcu_destroy_backlight(pcu);
- if (pcu->device_id != IMS_PCU_PCU_B_DEVICE_ID)
- sysfs_remove_group(&pcu->dev->kobj,
- &ims_pcu_ofn_attr_group);
- }
- }
- static int ims_pcu_init_bootloader_mode(struct ims_pcu *pcu)
- {
- int error;
- error = ims_pcu_execute_bl_command(pcu, QUERY_DEVICE, NULL, 0,
- IMS_PCU_CMD_RESPONSE_TIMEOUT);
- if (error) {
- dev_err(pcu->dev, "Bootloader does not respond, aborting\n");
- return error;
- }
- pcu->fw_start_addr =
- get_unaligned_le32(&pcu->cmd_buf[IMS_PCU_DATA_OFFSET + 11]);
- pcu->fw_end_addr =
- get_unaligned_le32(&pcu->cmd_buf[IMS_PCU_DATA_OFFSET + 15]);
- dev_info(pcu->dev,
- "Device is in bootloader mode (addr 0x%08x-0x%08x), requesting firmware\n",
- pcu->fw_start_addr, pcu->fw_end_addr);
- error = request_firmware_nowait(THIS_MODULE, true,
- IMS_PCU_FIRMWARE_NAME,
- pcu->dev, GFP_KERNEL, pcu,
- ims_pcu_process_async_firmware);
- if (error) {
- /* This error is not fatal, let userspace have another chance */
- complete(&pcu->async_firmware_done);
- }
- return 0;
- }
- static void ims_pcu_destroy_bootloader_mode(struct ims_pcu *pcu)
- {
- /* Make sure our initial firmware request has completed */
- wait_for_completion(&pcu->async_firmware_done);
- }
- #define IMS_PCU_APPLICATION_MODE 0
- #define IMS_PCU_BOOTLOADER_MODE 1
- static struct usb_driver ims_pcu_driver;
- static int ims_pcu_probe(struct usb_interface *intf,
- const struct usb_device_id *id)
- {
- struct usb_device *udev = interface_to_usbdev(intf);
- struct ims_pcu *pcu;
- int error;
- pcu = kzalloc(sizeof(struct ims_pcu), GFP_KERNEL);
- if (!pcu)
- return -ENOMEM;
- pcu->dev = &intf->dev;
- pcu->udev = udev;
- pcu->bootloader_mode = id->driver_info == IMS_PCU_BOOTLOADER_MODE;
- mutex_init(&pcu->cmd_mutex);
- init_completion(&pcu->cmd_done);
- init_completion(&pcu->async_firmware_done);
- error = ims_pcu_parse_cdc_data(intf, pcu);
- if (error)
- goto err_free_mem;
- error = usb_driver_claim_interface(&ims_pcu_driver,
- pcu->data_intf, pcu);
- if (error) {
- dev_err(&intf->dev,
- "Unable to claim corresponding data interface: %d\n",
- error);
- goto err_free_mem;
- }
- usb_set_intfdata(pcu->ctrl_intf, pcu);
- usb_set_intfdata(pcu->data_intf, pcu);
- error = ims_pcu_buffers_alloc(pcu);
- if (error)
- goto err_unclaim_intf;
- error = ims_pcu_start_io(pcu);
- if (error)
- goto err_free_buffers;
- error = ims_pcu_line_setup(pcu);
- if (error)
- goto err_stop_io;
- error = sysfs_create_group(&intf->dev.kobj, &ims_pcu_attr_group);
- if (error)
- goto err_stop_io;
- error = pcu->bootloader_mode ?
- ims_pcu_init_bootloader_mode(pcu) :
- ims_pcu_init_application_mode(pcu);
- if (error)
- goto err_remove_sysfs;
- return 0;
- err_remove_sysfs:
- sysfs_remove_group(&intf->dev.kobj, &ims_pcu_attr_group);
- err_stop_io:
- ims_pcu_stop_io(pcu);
- err_free_buffers:
- ims_pcu_buffers_free(pcu);
- err_unclaim_intf:
- usb_driver_release_interface(&ims_pcu_driver, pcu->data_intf);
- err_free_mem:
- kfree(pcu);
- return error;
- }
- static void ims_pcu_disconnect(struct usb_interface *intf)
- {
- struct ims_pcu *pcu = usb_get_intfdata(intf);
- struct usb_host_interface *alt = intf->cur_altsetting;
- usb_set_intfdata(intf, NULL);
- /*
- * See if we are dealing with control or data interface. The cleanup
- * happens when we unbind primary (control) interface.
- */
- if (alt->desc.bInterfaceClass != USB_CLASS_COMM)
- return;
- sysfs_remove_group(&intf->dev.kobj, &ims_pcu_attr_group);
- ims_pcu_stop_io(pcu);
- if (pcu->bootloader_mode)
- ims_pcu_destroy_bootloader_mode(pcu);
- else
- ims_pcu_destroy_application_mode(pcu);
- ims_pcu_buffers_free(pcu);
- kfree(pcu);
- }
- #ifdef CONFIG_PM
- static int ims_pcu_suspend(struct usb_interface *intf,
- pm_message_t message)
- {
- struct ims_pcu *pcu = usb_get_intfdata(intf);
- struct usb_host_interface *alt = intf->cur_altsetting;
- if (alt->desc.bInterfaceClass == USB_CLASS_COMM)
- ims_pcu_stop_io(pcu);
- return 0;
- }
- static int ims_pcu_resume(struct usb_interface *intf)
- {
- struct ims_pcu *pcu = usb_get_intfdata(intf);
- struct usb_host_interface *alt = intf->cur_altsetting;
- int retval = 0;
- if (alt->desc.bInterfaceClass == USB_CLASS_COMM) {
- retval = ims_pcu_start_io(pcu);
- if (retval == 0)
- retval = ims_pcu_line_setup(pcu);
- }
- return retval;
- }
- #endif
- static const struct usb_device_id ims_pcu_id_table[] = {
- {
- USB_DEVICE_AND_INTERFACE_INFO(0x04d8, 0x0082,
- USB_CLASS_COMM,
- USB_CDC_SUBCLASS_ACM,
- USB_CDC_ACM_PROTO_AT_V25TER),
- .driver_info = IMS_PCU_APPLICATION_MODE,
- },
- {
- USB_DEVICE_AND_INTERFACE_INFO(0x04d8, 0x0083,
- USB_CLASS_COMM,
- USB_CDC_SUBCLASS_ACM,
- USB_CDC_ACM_PROTO_AT_V25TER),
- .driver_info = IMS_PCU_BOOTLOADER_MODE,
- },
- { }
- };
- static struct usb_driver ims_pcu_driver = {
- .name = "ims_pcu",
- .id_table = ims_pcu_id_table,
- .probe = ims_pcu_probe,
- .disconnect = ims_pcu_disconnect,
- #ifdef CONFIG_PM
- .suspend = ims_pcu_suspend,
- .resume = ims_pcu_resume,
- .reset_resume = ims_pcu_resume,
- #endif
- };
- module_usb_driver(ims_pcu_driver);
- MODULE_DESCRIPTION("IMS Passenger Control Unit driver");
- MODULE_AUTHOR("Dmitry Torokhov <dmitry.torokhov@gmail.com>");
- MODULE_LICENSE("GPL");
|