xref: /linux/drivers/media/common/cypress_firmware.c (revision 58e16d792a6a8c6b750f637a4649967fcac853dc)
1*09c434b8SThomas Gleixner // SPDX-License-Identifier: GPL-2.0-only
279a63c60SHans Verkuil /*  cypress_firmware.c is part of the DVB USB library.
379a63c60SHans Verkuil  *
499e44da7SPatrick Boettcher  * Copyright (C) 2004-6 Patrick Boettcher (patrick.boettcher@posteo.de)
579a63c60SHans Verkuil  * see dvb-usb-init.c for copyright information.
679a63c60SHans Verkuil  *
779a63c60SHans Verkuil  * This file contains functions for downloading the firmware to Cypress FX 1
879a63c60SHans Verkuil  * and 2 based devices.
979a63c60SHans Verkuil  *
1079a63c60SHans Verkuil  */
1179a63c60SHans Verkuil 
1279a63c60SHans Verkuil #include <linux/module.h>
1379a63c60SHans Verkuil #include <linux/slab.h>
1479a63c60SHans Verkuil #include <linux/usb.h>
1579a63c60SHans Verkuil #include <linux/firmware.h>
1679a63c60SHans Verkuil #include "cypress_firmware.h"
1779a63c60SHans Verkuil 
1879a63c60SHans Verkuil struct usb_cypress_controller {
1979a63c60SHans Verkuil 	u8 id;
2079a63c60SHans Verkuil 	const char *name;	/* name of the usb controller */
2179a63c60SHans Verkuil 	u16 cs_reg;		/* needs to be restarted,
2279a63c60SHans Verkuil 				 * when the firmware has been downloaded */
2379a63c60SHans Verkuil };
2479a63c60SHans Verkuil 
2579a63c60SHans Verkuil static const struct usb_cypress_controller cypress[] = {
2679a63c60SHans Verkuil 	{ .id = CYPRESS_AN2135, .name = "Cypress AN2135", .cs_reg = 0x7f92 },
2779a63c60SHans Verkuil 	{ .id = CYPRESS_AN2235, .name = "Cypress AN2235", .cs_reg = 0x7f92 },
2879a63c60SHans Verkuil 	{ .id = CYPRESS_FX2,    .name = "Cypress FX2",    .cs_reg = 0xe600 },
2979a63c60SHans Verkuil };
3079a63c60SHans Verkuil 
3179a63c60SHans Verkuil /*
3279a63c60SHans Verkuil  * load a firmware packet to the device
3379a63c60SHans Verkuil  */
usb_cypress_writemem(struct usb_device * udev,u16 addr,u8 * data,u8 len)3479a63c60SHans Verkuil static int usb_cypress_writemem(struct usb_device *udev, u16 addr, u8 *data,
3579a63c60SHans Verkuil 		u8 len)
3679a63c60SHans Verkuil {
3779a63c60SHans Verkuil 	return usb_control_msg(udev, usb_sndctrlpipe(udev, 0),
3879a63c60SHans Verkuil 			0xa0, USB_TYPE_VENDOR, addr, 0x00, data, len, 5000);
3979a63c60SHans Verkuil }
4079a63c60SHans Verkuil 
cypress_get_hexline(const struct firmware * fw,struct hexline * hx,int * pos)4179a63c60SHans Verkuil static int cypress_get_hexline(const struct firmware *fw,
4279a63c60SHans Verkuil 				struct hexline *hx, int *pos)
4379a63c60SHans Verkuil {
4479a63c60SHans Verkuil 	u8 *b = (u8 *) &fw->data[*pos];
4579a63c60SHans Verkuil 	int data_offs = 4;
4679a63c60SHans Verkuil 
4779a63c60SHans Verkuil 	if (*pos >= fw->size)
4879a63c60SHans Verkuil 		return 0;
4979a63c60SHans Verkuil 
5079a63c60SHans Verkuil 	memset(hx, 0, sizeof(struct hexline));
5179a63c60SHans Verkuil 	hx->len = b[0];
5279a63c60SHans Verkuil 
5379a63c60SHans Verkuil 	if ((*pos + hx->len + 4) >= fw->size)
5479a63c60SHans Verkuil 		return -EINVAL;
5579a63c60SHans Verkuil 
5679a63c60SHans Verkuil 	hx->addr = b[1] | (b[2] << 8);
5779a63c60SHans Verkuil 	hx->type = b[3];
5879a63c60SHans Verkuil 
5979a63c60SHans Verkuil 	if (hx->type == 0x04) {
6079a63c60SHans Verkuil 		/* b[4] and b[5] are the Extended linear address record data
6179a63c60SHans Verkuil 		 * field */
6279a63c60SHans Verkuil 		hx->addr |= (b[4] << 24) | (b[5] << 16);
6379a63c60SHans Verkuil 	}
6479a63c60SHans Verkuil 
6579a63c60SHans Verkuil 	memcpy(hx->data, &b[data_offs], hx->len);
6679a63c60SHans Verkuil 	hx->chk = b[hx->len + data_offs];
6779a63c60SHans Verkuil 	*pos += hx->len + 5;
6879a63c60SHans Verkuil 
6979a63c60SHans Verkuil 	return *pos;
7079a63c60SHans Verkuil }
7179a63c60SHans Verkuil 
cypress_load_firmware(struct usb_device * udev,const struct firmware * fw,int type)7279a63c60SHans Verkuil int cypress_load_firmware(struct usb_device *udev,
7379a63c60SHans Verkuil 		const struct firmware *fw, int type)
7479a63c60SHans Verkuil {
7579a63c60SHans Verkuil 	struct hexline *hx;
7679a63c60SHans Verkuil 	int ret, pos = 0;
7779a63c60SHans Verkuil 
782d3da59fSMarkus Elfring 	hx = kmalloc(sizeof(*hx), GFP_KERNEL);
79c38e8657SMarkus Elfring 	if (!hx)
8079a63c60SHans Verkuil 		return -ENOMEM;
8179a63c60SHans Verkuil 
8279a63c60SHans Verkuil 	/* stop the CPU */
8379a63c60SHans Verkuil 	hx->data[0] = 1;
8479a63c60SHans Verkuil 	ret = usb_cypress_writemem(udev, cypress[type].cs_reg, hx->data, 1);
8579a63c60SHans Verkuil 	if (ret != 1) {
8679a63c60SHans Verkuil 		dev_err(&udev->dev, "%s: CPU stop failed=%d\n",
8779a63c60SHans Verkuil 				KBUILD_MODNAME, ret);
8879a63c60SHans Verkuil 		ret = -EIO;
8979a63c60SHans Verkuil 		goto err_kfree;
9079a63c60SHans Verkuil 	}
9179a63c60SHans Verkuil 
9279a63c60SHans Verkuil 	/* write firmware to memory */
9379a63c60SHans Verkuil 	for (;;) {
9479a63c60SHans Verkuil 		ret = cypress_get_hexline(fw, hx, &pos);
9579a63c60SHans Verkuil 		if (ret < 0)
9679a63c60SHans Verkuil 			goto err_kfree;
9779a63c60SHans Verkuil 		else if (ret == 0)
9879a63c60SHans Verkuil 			break;
9979a63c60SHans Verkuil 
10079a63c60SHans Verkuil 		ret = usb_cypress_writemem(udev, hx->addr, hx->data, hx->len);
10179a63c60SHans Verkuil 		if (ret < 0) {
10279a63c60SHans Verkuil 			goto err_kfree;
10379a63c60SHans Verkuil 		} else if (ret != hx->len) {
10479a63c60SHans Verkuil 			dev_err(&udev->dev,
10579a63c60SHans Verkuil 					"%s: error while transferring firmware (transferred size=%d, block size=%d)\n",
10679a63c60SHans Verkuil 					KBUILD_MODNAME, ret, hx->len);
10779a63c60SHans Verkuil 			ret = -EIO;
10879a63c60SHans Verkuil 			goto err_kfree;
10979a63c60SHans Verkuil 		}
11079a63c60SHans Verkuil 	}
11179a63c60SHans Verkuil 
11279a63c60SHans Verkuil 	/* start the CPU */
11379a63c60SHans Verkuil 	hx->data[0] = 0;
11479a63c60SHans Verkuil 	ret = usb_cypress_writemem(udev, cypress[type].cs_reg, hx->data, 1);
11579a63c60SHans Verkuil 	if (ret != 1) {
11679a63c60SHans Verkuil 		dev_err(&udev->dev, "%s: CPU start failed=%d\n",
11779a63c60SHans Verkuil 				KBUILD_MODNAME, ret);
11879a63c60SHans Verkuil 		ret = -EIO;
11979a63c60SHans Verkuil 		goto err_kfree;
12079a63c60SHans Verkuil 	}
12179a63c60SHans Verkuil 
12279a63c60SHans Verkuil 	ret = 0;
12379a63c60SHans Verkuil err_kfree:
12479a63c60SHans Verkuil 	kfree(hx);
12579a63c60SHans Verkuil 	return ret;
12679a63c60SHans Verkuil }
12779a63c60SHans Verkuil EXPORT_SYMBOL(cypress_load_firmware);
12879a63c60SHans Verkuil 
12979a63c60SHans Verkuil MODULE_AUTHOR("Antti Palosaari <crope@iki.fi>");
13079a63c60SHans Verkuil MODULE_DESCRIPTION("Cypress firmware download");
13179a63c60SHans Verkuil MODULE_LICENSE("GPL");
132