162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 262306a36Sopenharmony_ci/* dvb-usb-firmware.c is part of the DVB USB library. 362306a36Sopenharmony_ci * 462306a36Sopenharmony_ci * Copyright (C) 2004-6 Patrick Boettcher (patrick.boettcher@posteo.de) 562306a36Sopenharmony_ci * see dvb-usb-init.c for copyright information. 662306a36Sopenharmony_ci * 762306a36Sopenharmony_ci * This file contains functions for downloading the firmware to Cypress FX 1 and 2 based devices. 862306a36Sopenharmony_ci * 962306a36Sopenharmony_ci * FIXME: This part does actually not belong to dvb-usb, but to the usb-subsystem. 1062306a36Sopenharmony_ci */ 1162306a36Sopenharmony_ci#include "dvb-usb-common.h" 1262306a36Sopenharmony_ci 1362306a36Sopenharmony_ci#include <linux/usb.h> 1462306a36Sopenharmony_ci 1562306a36Sopenharmony_cistruct usb_cypress_controller { 1662306a36Sopenharmony_ci int id; 1762306a36Sopenharmony_ci const char *name; /* name of the usb controller */ 1862306a36Sopenharmony_ci u16 cpu_cs_register; /* needs to be restarted, when the firmware has been downloaded. */ 1962306a36Sopenharmony_ci}; 2062306a36Sopenharmony_ci 2162306a36Sopenharmony_cistatic struct usb_cypress_controller cypress[] = { 2262306a36Sopenharmony_ci { .id = DEVICE_SPECIFIC, .name = "Device specific", .cpu_cs_register = 0 }, 2362306a36Sopenharmony_ci { .id = CYPRESS_AN2135, .name = "Cypress AN2135", .cpu_cs_register = 0x7f92 }, 2462306a36Sopenharmony_ci { .id = CYPRESS_AN2235, .name = "Cypress AN2235", .cpu_cs_register = 0x7f92 }, 2562306a36Sopenharmony_ci { .id = CYPRESS_FX2, .name = "Cypress FX2", .cpu_cs_register = 0xe600 }, 2662306a36Sopenharmony_ci}; 2762306a36Sopenharmony_ci 2862306a36Sopenharmony_ci/* 2962306a36Sopenharmony_ci * load a firmware packet to the device 3062306a36Sopenharmony_ci */ 3162306a36Sopenharmony_cistatic int usb_cypress_writemem(struct usb_device *udev,u16 addr,u8 *data, u8 len) 3262306a36Sopenharmony_ci{ 3362306a36Sopenharmony_ci return usb_control_msg(udev, usb_sndctrlpipe(udev,0), 3462306a36Sopenharmony_ci 0xa0, USB_TYPE_VENDOR, addr, 0x00, data, len, 5000); 3562306a36Sopenharmony_ci} 3662306a36Sopenharmony_ci 3762306a36Sopenharmony_ciint usb_cypress_load_firmware(struct usb_device *udev, const struct firmware *fw, int type) 3862306a36Sopenharmony_ci{ 3962306a36Sopenharmony_ci struct hexline *hx; 4062306a36Sopenharmony_ci u8 *buf; 4162306a36Sopenharmony_ci int ret, pos = 0; 4262306a36Sopenharmony_ci u16 cpu_cs_register = cypress[type].cpu_cs_register; 4362306a36Sopenharmony_ci 4462306a36Sopenharmony_ci buf = kmalloc(sizeof(*hx), GFP_KERNEL); 4562306a36Sopenharmony_ci if (!buf) 4662306a36Sopenharmony_ci return -ENOMEM; 4762306a36Sopenharmony_ci hx = (struct hexline *)buf; 4862306a36Sopenharmony_ci 4962306a36Sopenharmony_ci /* stop the CPU */ 5062306a36Sopenharmony_ci buf[0] = 1; 5162306a36Sopenharmony_ci if (usb_cypress_writemem(udev, cpu_cs_register, buf, 1) != 1) 5262306a36Sopenharmony_ci err("could not stop the USB controller CPU."); 5362306a36Sopenharmony_ci 5462306a36Sopenharmony_ci while ((ret = dvb_usb_get_hexline(fw, hx, &pos)) > 0) { 5562306a36Sopenharmony_ci deb_fw("writing to address 0x%04x (buffer: 0x%02x %02x)\n", hx->addr, hx->len, hx->chk); 5662306a36Sopenharmony_ci ret = usb_cypress_writemem(udev, hx->addr, hx->data, hx->len); 5762306a36Sopenharmony_ci 5862306a36Sopenharmony_ci if (ret != hx->len) { 5962306a36Sopenharmony_ci err("error while transferring firmware (transferred size: %d, block size: %d)", 6062306a36Sopenharmony_ci ret, hx->len); 6162306a36Sopenharmony_ci ret = -EINVAL; 6262306a36Sopenharmony_ci break; 6362306a36Sopenharmony_ci } 6462306a36Sopenharmony_ci } 6562306a36Sopenharmony_ci if (ret < 0) { 6662306a36Sopenharmony_ci err("firmware download failed at %d with %d",pos,ret); 6762306a36Sopenharmony_ci kfree(buf); 6862306a36Sopenharmony_ci return ret; 6962306a36Sopenharmony_ci } 7062306a36Sopenharmony_ci 7162306a36Sopenharmony_ci if (ret == 0) { 7262306a36Sopenharmony_ci /* restart the CPU */ 7362306a36Sopenharmony_ci buf[0] = 0; 7462306a36Sopenharmony_ci if (usb_cypress_writemem(udev, cpu_cs_register, buf, 1) != 1) { 7562306a36Sopenharmony_ci err("could not restart the USB controller CPU."); 7662306a36Sopenharmony_ci ret = -EINVAL; 7762306a36Sopenharmony_ci } 7862306a36Sopenharmony_ci } else 7962306a36Sopenharmony_ci ret = -EIO; 8062306a36Sopenharmony_ci 8162306a36Sopenharmony_ci kfree(buf); 8262306a36Sopenharmony_ci 8362306a36Sopenharmony_ci return ret; 8462306a36Sopenharmony_ci} 8562306a36Sopenharmony_ciEXPORT_SYMBOL(usb_cypress_load_firmware); 8662306a36Sopenharmony_ci 8762306a36Sopenharmony_ciint dvb_usb_download_firmware(struct usb_device *udev, 8862306a36Sopenharmony_ci const struct dvb_usb_device_properties *props) 8962306a36Sopenharmony_ci{ 9062306a36Sopenharmony_ci int ret; 9162306a36Sopenharmony_ci const struct firmware *fw = NULL; 9262306a36Sopenharmony_ci 9362306a36Sopenharmony_ci if ((ret = request_firmware(&fw, props->firmware, &udev->dev)) != 0) { 9462306a36Sopenharmony_ci err("did not find the firmware file '%s' (status %d). You can use <kernel_dir>/scripts/get_dvb_firmware to get the firmware", 9562306a36Sopenharmony_ci props->firmware,ret); 9662306a36Sopenharmony_ci return ret; 9762306a36Sopenharmony_ci } 9862306a36Sopenharmony_ci 9962306a36Sopenharmony_ci info("downloading firmware from file '%s'",props->firmware); 10062306a36Sopenharmony_ci 10162306a36Sopenharmony_ci switch (props->usb_ctrl) { 10262306a36Sopenharmony_ci case CYPRESS_AN2135: 10362306a36Sopenharmony_ci case CYPRESS_AN2235: 10462306a36Sopenharmony_ci case CYPRESS_FX2: 10562306a36Sopenharmony_ci ret = usb_cypress_load_firmware(udev, fw, props->usb_ctrl); 10662306a36Sopenharmony_ci break; 10762306a36Sopenharmony_ci case DEVICE_SPECIFIC: 10862306a36Sopenharmony_ci if (props->download_firmware) 10962306a36Sopenharmony_ci ret = props->download_firmware(udev,fw); 11062306a36Sopenharmony_ci else { 11162306a36Sopenharmony_ci err("BUG: driver didn't specified a download_firmware-callback, although it claims to have a DEVICE_SPECIFIC one."); 11262306a36Sopenharmony_ci ret = -EINVAL; 11362306a36Sopenharmony_ci } 11462306a36Sopenharmony_ci break; 11562306a36Sopenharmony_ci default: 11662306a36Sopenharmony_ci ret = -EINVAL; 11762306a36Sopenharmony_ci break; 11862306a36Sopenharmony_ci } 11962306a36Sopenharmony_ci 12062306a36Sopenharmony_ci release_firmware(fw); 12162306a36Sopenharmony_ci return ret; 12262306a36Sopenharmony_ci} 12362306a36Sopenharmony_ci 12462306a36Sopenharmony_ciint dvb_usb_get_hexline(const struct firmware *fw, struct hexline *hx, 12562306a36Sopenharmony_ci int *pos) 12662306a36Sopenharmony_ci{ 12762306a36Sopenharmony_ci u8 *b = (u8 *) &fw->data[*pos]; 12862306a36Sopenharmony_ci int data_offs = 4; 12962306a36Sopenharmony_ci if (*pos >= fw->size) 13062306a36Sopenharmony_ci return 0; 13162306a36Sopenharmony_ci 13262306a36Sopenharmony_ci memset(hx,0,sizeof(struct hexline)); 13362306a36Sopenharmony_ci 13462306a36Sopenharmony_ci hx->len = b[0]; 13562306a36Sopenharmony_ci 13662306a36Sopenharmony_ci if ((*pos + hx->len + 4) >= fw->size) 13762306a36Sopenharmony_ci return -EINVAL; 13862306a36Sopenharmony_ci 13962306a36Sopenharmony_ci hx->addr = b[1] | (b[2] << 8); 14062306a36Sopenharmony_ci hx->type = b[3]; 14162306a36Sopenharmony_ci 14262306a36Sopenharmony_ci if (hx->type == 0x04) { 14362306a36Sopenharmony_ci /* b[4] and b[5] are the Extended linear address record data field */ 14462306a36Sopenharmony_ci hx->addr |= (b[4] << 24) | (b[5] << 16); 14562306a36Sopenharmony_ci/* hx->len -= 2; 14662306a36Sopenharmony_ci data_offs += 2; */ 14762306a36Sopenharmony_ci } 14862306a36Sopenharmony_ci memcpy(hx->data,&b[data_offs],hx->len); 14962306a36Sopenharmony_ci hx->chk = b[hx->len + data_offs]; 15062306a36Sopenharmony_ci 15162306a36Sopenharmony_ci *pos += hx->len + 5; 15262306a36Sopenharmony_ci 15362306a36Sopenharmony_ci return *pos; 15462306a36Sopenharmony_ci} 15562306a36Sopenharmony_ciEXPORT_SYMBOL(dvb_usb_get_hexline); 156