162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 262306a36Sopenharmony_ci/* cypress_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 862306a36Sopenharmony_ci * and 2 based devices. 962306a36Sopenharmony_ci * 1062306a36Sopenharmony_ci */ 1162306a36Sopenharmony_ci 1262306a36Sopenharmony_ci#include <linux/module.h> 1362306a36Sopenharmony_ci#include <linux/slab.h> 1462306a36Sopenharmony_ci#include <linux/usb.h> 1562306a36Sopenharmony_ci#include <linux/firmware.h> 1662306a36Sopenharmony_ci#include "cypress_firmware.h" 1762306a36Sopenharmony_ci 1862306a36Sopenharmony_cistruct usb_cypress_controller { 1962306a36Sopenharmony_ci u8 id; 2062306a36Sopenharmony_ci const char *name; /* name of the usb controller */ 2162306a36Sopenharmony_ci u16 cs_reg; /* needs to be restarted, 2262306a36Sopenharmony_ci * when the firmware has been downloaded */ 2362306a36Sopenharmony_ci}; 2462306a36Sopenharmony_ci 2562306a36Sopenharmony_cistatic const struct usb_cypress_controller cypress[] = { 2662306a36Sopenharmony_ci { .id = CYPRESS_AN2135, .name = "Cypress AN2135", .cs_reg = 0x7f92 }, 2762306a36Sopenharmony_ci { .id = CYPRESS_AN2235, .name = "Cypress AN2235", .cs_reg = 0x7f92 }, 2862306a36Sopenharmony_ci { .id = CYPRESS_FX2, .name = "Cypress FX2", .cs_reg = 0xe600 }, 2962306a36Sopenharmony_ci}; 3062306a36Sopenharmony_ci 3162306a36Sopenharmony_ci/* 3262306a36Sopenharmony_ci * load a firmware packet to the device 3362306a36Sopenharmony_ci */ 3462306a36Sopenharmony_cistatic int usb_cypress_writemem(struct usb_device *udev, u16 addr, u8 *data, 3562306a36Sopenharmony_ci u8 len) 3662306a36Sopenharmony_ci{ 3762306a36Sopenharmony_ci return usb_control_msg(udev, usb_sndctrlpipe(udev, 0), 3862306a36Sopenharmony_ci 0xa0, USB_TYPE_VENDOR, addr, 0x00, data, len, 5000); 3962306a36Sopenharmony_ci} 4062306a36Sopenharmony_ci 4162306a36Sopenharmony_cistatic int cypress_get_hexline(const struct firmware *fw, 4262306a36Sopenharmony_ci struct hexline *hx, int *pos) 4362306a36Sopenharmony_ci{ 4462306a36Sopenharmony_ci u8 *b = (u8 *) &fw->data[*pos]; 4562306a36Sopenharmony_ci int data_offs = 4; 4662306a36Sopenharmony_ci 4762306a36Sopenharmony_ci if (*pos >= fw->size) 4862306a36Sopenharmony_ci return 0; 4962306a36Sopenharmony_ci 5062306a36Sopenharmony_ci memset(hx, 0, sizeof(struct hexline)); 5162306a36Sopenharmony_ci hx->len = b[0]; 5262306a36Sopenharmony_ci 5362306a36Sopenharmony_ci if ((*pos + hx->len + 4) >= fw->size) 5462306a36Sopenharmony_ci return -EINVAL; 5562306a36Sopenharmony_ci 5662306a36Sopenharmony_ci hx->addr = b[1] | (b[2] << 8); 5762306a36Sopenharmony_ci hx->type = b[3]; 5862306a36Sopenharmony_ci 5962306a36Sopenharmony_ci if (hx->type == 0x04) { 6062306a36Sopenharmony_ci /* b[4] and b[5] are the Extended linear address record data 6162306a36Sopenharmony_ci * field */ 6262306a36Sopenharmony_ci hx->addr |= (b[4] << 24) | (b[5] << 16); 6362306a36Sopenharmony_ci } 6462306a36Sopenharmony_ci 6562306a36Sopenharmony_ci memcpy(hx->data, &b[data_offs], hx->len); 6662306a36Sopenharmony_ci hx->chk = b[hx->len + data_offs]; 6762306a36Sopenharmony_ci *pos += hx->len + 5; 6862306a36Sopenharmony_ci 6962306a36Sopenharmony_ci return *pos; 7062306a36Sopenharmony_ci} 7162306a36Sopenharmony_ci 7262306a36Sopenharmony_ciint cypress_load_firmware(struct usb_device *udev, 7362306a36Sopenharmony_ci const struct firmware *fw, int type) 7462306a36Sopenharmony_ci{ 7562306a36Sopenharmony_ci struct hexline *hx; 7662306a36Sopenharmony_ci int ret, pos = 0; 7762306a36Sopenharmony_ci 7862306a36Sopenharmony_ci hx = kmalloc(sizeof(*hx), GFP_KERNEL); 7962306a36Sopenharmony_ci if (!hx) 8062306a36Sopenharmony_ci return -ENOMEM; 8162306a36Sopenharmony_ci 8262306a36Sopenharmony_ci /* stop the CPU */ 8362306a36Sopenharmony_ci hx->data[0] = 1; 8462306a36Sopenharmony_ci ret = usb_cypress_writemem(udev, cypress[type].cs_reg, hx->data, 1); 8562306a36Sopenharmony_ci if (ret != 1) { 8662306a36Sopenharmony_ci dev_err(&udev->dev, "%s: CPU stop failed=%d\n", 8762306a36Sopenharmony_ci KBUILD_MODNAME, ret); 8862306a36Sopenharmony_ci ret = -EIO; 8962306a36Sopenharmony_ci goto err_kfree; 9062306a36Sopenharmony_ci } 9162306a36Sopenharmony_ci 9262306a36Sopenharmony_ci /* write firmware to memory */ 9362306a36Sopenharmony_ci for (;;) { 9462306a36Sopenharmony_ci ret = cypress_get_hexline(fw, hx, &pos); 9562306a36Sopenharmony_ci if (ret < 0) 9662306a36Sopenharmony_ci goto err_kfree; 9762306a36Sopenharmony_ci else if (ret == 0) 9862306a36Sopenharmony_ci break; 9962306a36Sopenharmony_ci 10062306a36Sopenharmony_ci ret = usb_cypress_writemem(udev, hx->addr, hx->data, hx->len); 10162306a36Sopenharmony_ci if (ret < 0) { 10262306a36Sopenharmony_ci goto err_kfree; 10362306a36Sopenharmony_ci } else if (ret != hx->len) { 10462306a36Sopenharmony_ci dev_err(&udev->dev, 10562306a36Sopenharmony_ci "%s: error while transferring firmware (transferred size=%d, block size=%d)\n", 10662306a36Sopenharmony_ci KBUILD_MODNAME, ret, hx->len); 10762306a36Sopenharmony_ci ret = -EIO; 10862306a36Sopenharmony_ci goto err_kfree; 10962306a36Sopenharmony_ci } 11062306a36Sopenharmony_ci } 11162306a36Sopenharmony_ci 11262306a36Sopenharmony_ci /* start the CPU */ 11362306a36Sopenharmony_ci hx->data[0] = 0; 11462306a36Sopenharmony_ci ret = usb_cypress_writemem(udev, cypress[type].cs_reg, hx->data, 1); 11562306a36Sopenharmony_ci if (ret != 1) { 11662306a36Sopenharmony_ci dev_err(&udev->dev, "%s: CPU start failed=%d\n", 11762306a36Sopenharmony_ci KBUILD_MODNAME, ret); 11862306a36Sopenharmony_ci ret = -EIO; 11962306a36Sopenharmony_ci goto err_kfree; 12062306a36Sopenharmony_ci } 12162306a36Sopenharmony_ci 12262306a36Sopenharmony_ci ret = 0; 12362306a36Sopenharmony_cierr_kfree: 12462306a36Sopenharmony_ci kfree(hx); 12562306a36Sopenharmony_ci return ret; 12662306a36Sopenharmony_ci} 12762306a36Sopenharmony_ciEXPORT_SYMBOL(cypress_load_firmware); 12862306a36Sopenharmony_ci 12962306a36Sopenharmony_ciMODULE_AUTHOR("Antti Palosaari <crope@iki.fi>"); 13062306a36Sopenharmony_ciMODULE_DESCRIPTION("Cypress firmware download"); 13162306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 132