1 // SPDX-License-Identifier: GPL-2.0-only 2 /* cypress_firmware.c is part of the DVB USB library. 3 * 4 * Copyright (C) 2004-6 Patrick Boettcher (patrick.boettcher@posteo.de) 5 * see dvb-usb-init.c for copyright information. 6 * 7 * This file contains functions for downloading the firmware to Cypress FX 1 8 * and 2 based devices. 9 * 10 */ 11 12 #include <linux/module.h> 13 #include <linux/slab.h> 14 #include <linux/usb.h> 15 #include <linux/firmware.h> 16 #include "cypress_firmware.h" 17 18 struct usb_cypress_controller { 19 u8 id; 20 const char *name; /* name of the usb controller */ 21 u16 cs_reg; /* needs to be restarted, 22 * when the firmware has been downloaded */ 23 }; 24 25 static const struct usb_cypress_controller cypress[] = { 26 { .id = CYPRESS_AN2135, .name = "Cypress AN2135", .cs_reg = 0x7f92 }, 27 { .id = CYPRESS_AN2235, .name = "Cypress AN2235", .cs_reg = 0x7f92 }, 28 { .id = CYPRESS_FX2, .name = "Cypress FX2", .cs_reg = 0xe600 }, 29 }; 30 31 /* 32 * load a firmware packet to the device 33 */ 34 static int usb_cypress_writemem(struct usb_device *udev, u16 addr, u8 *data, 35 u8 len) 36 { 37 return usb_control_msg(udev, usb_sndctrlpipe(udev, 0), 38 0xa0, USB_TYPE_VENDOR, addr, 0x00, data, len, 5000); 39 } 40 41 static int cypress_get_hexline(const struct firmware *fw, 42 struct hexline *hx, int *pos) 43 { 44 u8 *b = (u8 *) &fw->data[*pos]; 45 int data_offs = 4; 46 47 if (*pos >= fw->size) 48 return 0; 49 50 memset(hx, 0, sizeof(struct hexline)); 51 hx->len = b[0]; 52 53 if ((*pos + hx->len + 4) >= fw->size) 54 return -EINVAL; 55 56 hx->addr = b[1] | (b[2] << 8); 57 hx->type = b[3]; 58 59 if (hx->type == 0x04) { 60 /* b[4] and b[5] are the Extended linear address record data 61 * field */ 62 hx->addr |= (b[4] << 24) | (b[5] << 16); 63 } 64 65 memcpy(hx->data, &b[data_offs], hx->len); 66 hx->chk = b[hx->len + data_offs]; 67 *pos += hx->len + 5; 68 69 return *pos; 70 } 71 72 int cypress_load_firmware(struct usb_device *udev, 73 const struct firmware *fw, int type) 74 { 75 struct hexline *hx; 76 int ret, pos = 0; 77 78 hx = kmalloc(sizeof(*hx), GFP_KERNEL); 79 if (!hx) 80 return -ENOMEM; 81 82 /* stop the CPU */ 83 hx->data[0] = 1; 84 ret = usb_cypress_writemem(udev, cypress[type].cs_reg, hx->data, 1); 85 if (ret != 1) { 86 dev_err(&udev->dev, "%s: CPU stop failed=%d\n", 87 KBUILD_MODNAME, ret); 88 ret = -EIO; 89 goto err_kfree; 90 } 91 92 /* write firmware to memory */ 93 for (;;) { 94 ret = cypress_get_hexline(fw, hx, &pos); 95 if (ret < 0) 96 goto err_kfree; 97 else if (ret == 0) 98 break; 99 100 ret = usb_cypress_writemem(udev, hx->addr, hx->data, hx->len); 101 if (ret < 0) { 102 goto err_kfree; 103 } else if (ret != hx->len) { 104 dev_err(&udev->dev, 105 "%s: error while transferring firmware (transferred size=%d, block size=%d)\n", 106 KBUILD_MODNAME, ret, hx->len); 107 ret = -EIO; 108 goto err_kfree; 109 } 110 } 111 112 /* start the CPU */ 113 hx->data[0] = 0; 114 ret = usb_cypress_writemem(udev, cypress[type].cs_reg, hx->data, 1); 115 if (ret != 1) { 116 dev_err(&udev->dev, "%s: CPU start failed=%d\n", 117 KBUILD_MODNAME, ret); 118 ret = -EIO; 119 goto err_kfree; 120 } 121 122 ret = 0; 123 err_kfree: 124 kfree(hx); 125 return ret; 126 } 127 EXPORT_SYMBOL(cypress_load_firmware); 128 129 MODULE_AUTHOR("Antti Palosaari <crope@iki.fi>"); 130 MODULE_DESCRIPTION("Cypress firmware download"); 131 MODULE_LICENSE("GPL"); 132