1355b4b2b5e83986132c77326e2b97780480b8d69Antti Palosaari/*  cypress_firmware.c is part of the DVB USB library.
2b00a901801f671a48feac6048faeafe0979760e6Antti Palosaari *
3b00a901801f671a48feac6048faeafe0979760e6Antti Palosaari * Copyright (C) 2004-6 Patrick Boettcher (patrick.boettcher@desy.de)
4b00a901801f671a48feac6048faeafe0979760e6Antti Palosaari * see dvb-usb-init.c for copyright information.
5b00a901801f671a48feac6048faeafe0979760e6Antti Palosaari *
6b00a901801f671a48feac6048faeafe0979760e6Antti Palosaari * This file contains functions for downloading the firmware to Cypress FX 1
7b00a901801f671a48feac6048faeafe0979760e6Antti Palosaari * and 2 based devices.
8b00a901801f671a48feac6048faeafe0979760e6Antti Palosaari *
9b00a901801f671a48feac6048faeafe0979760e6Antti Palosaari */
10b00a901801f671a48feac6048faeafe0979760e6Antti Palosaari
1179a63c60a6a2ae589e44529401e0ab1150e9408aHans Verkuil#include <linux/module.h>
1279a63c60a6a2ae589e44529401e0ab1150e9408aHans Verkuil#include <linux/slab.h>
1379a63c60a6a2ae589e44529401e0ab1150e9408aHans Verkuil#include <linux/usb.h>
1479a63c60a6a2ae589e44529401e0ab1150e9408aHans Verkuil#include <linux/firmware.h>
15355b4b2b5e83986132c77326e2b97780480b8d69Antti Palosaari#include "cypress_firmware.h"
16b00a901801f671a48feac6048faeafe0979760e6Antti Palosaari
17b00a901801f671a48feac6048faeafe0979760e6Antti Palosaaristruct usb_cypress_controller {
18b00a901801f671a48feac6048faeafe0979760e6Antti Palosaari	u8 id;
19b00a901801f671a48feac6048faeafe0979760e6Antti Palosaari	const char *name;	/* name of the usb controller */
20b00a901801f671a48feac6048faeafe0979760e6Antti Palosaari	u16 cs_reg;		/* needs to be restarted,
21b00a901801f671a48feac6048faeafe0979760e6Antti Palosaari				 * when the firmware has been downloaded */
22b00a901801f671a48feac6048faeafe0979760e6Antti Palosaari};
23b00a901801f671a48feac6048faeafe0979760e6Antti Palosaari
24b00a901801f671a48feac6048faeafe0979760e6Antti Palosaaristatic const struct usb_cypress_controller cypress[] = {
25b00a901801f671a48feac6048faeafe0979760e6Antti Palosaari	{ .id = CYPRESS_AN2135, .name = "Cypress AN2135", .cs_reg = 0x7f92 },
26b00a901801f671a48feac6048faeafe0979760e6Antti Palosaari	{ .id = CYPRESS_AN2235, .name = "Cypress AN2235", .cs_reg = 0x7f92 },
27b00a901801f671a48feac6048faeafe0979760e6Antti Palosaari	{ .id = CYPRESS_FX2,    .name = "Cypress FX2",    .cs_reg = 0xe600 },
28b00a901801f671a48feac6048faeafe0979760e6Antti Palosaari};
29b00a901801f671a48feac6048faeafe0979760e6Antti Palosaari
30b00a901801f671a48feac6048faeafe0979760e6Antti Palosaari/*
31b00a901801f671a48feac6048faeafe0979760e6Antti Palosaari * load a firmware packet to the device
32b00a901801f671a48feac6048faeafe0979760e6Antti Palosaari */
33b00a901801f671a48feac6048faeafe0979760e6Antti Palosaaristatic int usb_cypress_writemem(struct usb_device *udev, u16 addr, u8 *data,
34b00a901801f671a48feac6048faeafe0979760e6Antti Palosaari		u8 len)
35b00a901801f671a48feac6048faeafe0979760e6Antti Palosaari{
36b00a901801f671a48feac6048faeafe0979760e6Antti Palosaari	return usb_control_msg(udev, usb_sndctrlpipe(udev, 0),
37b00a901801f671a48feac6048faeafe0979760e6Antti Palosaari			0xa0, USB_TYPE_VENDOR, addr, 0x00, data, len, 5000);
38b00a901801f671a48feac6048faeafe0979760e6Antti Palosaari}
39b00a901801f671a48feac6048faeafe0979760e6Antti Palosaari
4079a63c60a6a2ae589e44529401e0ab1150e9408aHans Verkuilstatic int cypress_get_hexline(const struct firmware *fw,
4179a63c60a6a2ae589e44529401e0ab1150e9408aHans Verkuil				struct hexline *hx, int *pos)
4279a63c60a6a2ae589e44529401e0ab1150e9408aHans Verkuil{
4379a63c60a6a2ae589e44529401e0ab1150e9408aHans Verkuil	u8 *b = (u8 *) &fw->data[*pos];
4479a63c60a6a2ae589e44529401e0ab1150e9408aHans Verkuil	int data_offs = 4;
4579a63c60a6a2ae589e44529401e0ab1150e9408aHans Verkuil
4679a63c60a6a2ae589e44529401e0ab1150e9408aHans Verkuil	if (*pos >= fw->size)
4779a63c60a6a2ae589e44529401e0ab1150e9408aHans Verkuil		return 0;
4879a63c60a6a2ae589e44529401e0ab1150e9408aHans Verkuil
4979a63c60a6a2ae589e44529401e0ab1150e9408aHans Verkuil	memset(hx, 0, sizeof(struct hexline));
5079a63c60a6a2ae589e44529401e0ab1150e9408aHans Verkuil	hx->len = b[0];
5179a63c60a6a2ae589e44529401e0ab1150e9408aHans Verkuil
5279a63c60a6a2ae589e44529401e0ab1150e9408aHans Verkuil	if ((*pos + hx->len + 4) >= fw->size)
5379a63c60a6a2ae589e44529401e0ab1150e9408aHans Verkuil		return -EINVAL;
5479a63c60a6a2ae589e44529401e0ab1150e9408aHans Verkuil
5579a63c60a6a2ae589e44529401e0ab1150e9408aHans Verkuil	hx->addr = b[1] | (b[2] << 8);
5679a63c60a6a2ae589e44529401e0ab1150e9408aHans Verkuil	hx->type = b[3];
5779a63c60a6a2ae589e44529401e0ab1150e9408aHans Verkuil
5879a63c60a6a2ae589e44529401e0ab1150e9408aHans Verkuil	if (hx->type == 0x04) {
5979a63c60a6a2ae589e44529401e0ab1150e9408aHans Verkuil		/* b[4] and b[5] are the Extended linear address record data
6079a63c60a6a2ae589e44529401e0ab1150e9408aHans Verkuil		 * field */
6179a63c60a6a2ae589e44529401e0ab1150e9408aHans Verkuil		hx->addr |= (b[4] << 24) | (b[5] << 16);
6279a63c60a6a2ae589e44529401e0ab1150e9408aHans Verkuil	}
6379a63c60a6a2ae589e44529401e0ab1150e9408aHans Verkuil
6479a63c60a6a2ae589e44529401e0ab1150e9408aHans Verkuil	memcpy(hx->data, &b[data_offs], hx->len);
6579a63c60a6a2ae589e44529401e0ab1150e9408aHans Verkuil	hx->chk = b[hx->len + data_offs];
6679a63c60a6a2ae589e44529401e0ab1150e9408aHans Verkuil	*pos += hx->len + 5;
6779a63c60a6a2ae589e44529401e0ab1150e9408aHans Verkuil
6879a63c60a6a2ae589e44529401e0ab1150e9408aHans Verkuil	return *pos;
6979a63c60a6a2ae589e44529401e0ab1150e9408aHans Verkuil}
7079a63c60a6a2ae589e44529401e0ab1150e9408aHans Verkuil
7179a63c60a6a2ae589e44529401e0ab1150e9408aHans Verkuilint cypress_load_firmware(struct usb_device *udev,
72b00a901801f671a48feac6048faeafe0979760e6Antti Palosaari		const struct firmware *fw, int type)
73b00a901801f671a48feac6048faeafe0979760e6Antti Palosaari{
742347e6836ad2a5a2f7e62bd12b8f52fe15f04f74Antti Palosaari	struct hexline *hx;
75b00a901801f671a48feac6048faeafe0979760e6Antti Palosaari	int ret, pos = 0;
76b00a901801f671a48feac6048faeafe0979760e6Antti Palosaari
772347e6836ad2a5a2f7e62bd12b8f52fe15f04f74Antti Palosaari	hx = kmalloc(sizeof(struct hexline), GFP_KERNEL);
782347e6836ad2a5a2f7e62bd12b8f52fe15f04f74Antti Palosaari	if (!hx) {
792347e6836ad2a5a2f7e62bd12b8f52fe15f04f74Antti Palosaari		dev_err(&udev->dev, "%s: kmalloc() failed\n", KBUILD_MODNAME);
802347e6836ad2a5a2f7e62bd12b8f52fe15f04f74Antti Palosaari		return -ENOMEM;
812347e6836ad2a5a2f7e62bd12b8f52fe15f04f74Antti Palosaari	}
822347e6836ad2a5a2f7e62bd12b8f52fe15f04f74Antti Palosaari
83b00a901801f671a48feac6048faeafe0979760e6Antti Palosaari	/* stop the CPU */
842347e6836ad2a5a2f7e62bd12b8f52fe15f04f74Antti Palosaari	hx->data[0] = 1;
852347e6836ad2a5a2f7e62bd12b8f52fe15f04f74Antti Palosaari	ret = usb_cypress_writemem(udev, cypress[type].cs_reg, hx->data, 1);
862347e6836ad2a5a2f7e62bd12b8f52fe15f04f74Antti Palosaari	if (ret != 1) {
872347e6836ad2a5a2f7e62bd12b8f52fe15f04f74Antti Palosaari		dev_err(&udev->dev, "%s: CPU stop failed=%d\n",
882347e6836ad2a5a2f7e62bd12b8f52fe15f04f74Antti Palosaari				KBUILD_MODNAME, ret);
892347e6836ad2a5a2f7e62bd12b8f52fe15f04f74Antti Palosaari		ret = -EIO;
902347e6836ad2a5a2f7e62bd12b8f52fe15f04f74Antti Palosaari		goto err_kfree;
912347e6836ad2a5a2f7e62bd12b8f52fe15f04f74Antti Palosaari	}
922347e6836ad2a5a2f7e62bd12b8f52fe15f04f74Antti Palosaari
932347e6836ad2a5a2f7e62bd12b8f52fe15f04f74Antti Palosaari	/* write firmware to memory */
942347e6836ad2a5a2f7e62bd12b8f52fe15f04f74Antti Palosaari	for (;;) {
9579a63c60a6a2ae589e44529401e0ab1150e9408aHans Verkuil		ret = cypress_get_hexline(fw, hx, &pos);
962347e6836ad2a5a2f7e62bd12b8f52fe15f04f74Antti Palosaari		if (ret < 0)
972347e6836ad2a5a2f7e62bd12b8f52fe15f04f74Antti Palosaari			goto err_kfree;
982347e6836ad2a5a2f7e62bd12b8f52fe15f04f74Antti Palosaari		else if (ret == 0)
992347e6836ad2a5a2f7e62bd12b8f52fe15f04f74Antti Palosaari			break;
1002347e6836ad2a5a2f7e62bd12b8f52fe15f04f74Antti Palosaari
1012347e6836ad2a5a2f7e62bd12b8f52fe15f04f74Antti Palosaari		ret = usb_cypress_writemem(udev, hx->addr, hx->data, hx->len);
1022347e6836ad2a5a2f7e62bd12b8f52fe15f04f74Antti Palosaari		if (ret < 0) {
1032347e6836ad2a5a2f7e62bd12b8f52fe15f04f74Antti Palosaari			goto err_kfree;
1042347e6836ad2a5a2f7e62bd12b8f52fe15f04f74Antti Palosaari		} else if (ret != hx->len) {
10561356eea6742ad38ad9ecfd5c65e2662e4c9d8abAntti Palosaari			dev_err(&udev->dev,
10661356eea6742ad38ad9ecfd5c65e2662e4c9d8abAntti Palosaari					"%s: error while transferring firmware (transferred size=%d, block size=%d)\n",
1072347e6836ad2a5a2f7e62bd12b8f52fe15f04f74Antti Palosaari					KBUILD_MODNAME, ret, hx->len);
1082347e6836ad2a5a2f7e62bd12b8f52fe15f04f74Antti Palosaari			ret = -EIO;
1092347e6836ad2a5a2f7e62bd12b8f52fe15f04f74Antti Palosaari			goto err_kfree;
110b00a901801f671a48feac6048faeafe0979760e6Antti Palosaari		}
111b00a901801f671a48feac6048faeafe0979760e6Antti Palosaari	}
112b00a901801f671a48feac6048faeafe0979760e6Antti Palosaari
1132347e6836ad2a5a2f7e62bd12b8f52fe15f04f74Antti Palosaari	/* start the CPU */
1142347e6836ad2a5a2f7e62bd12b8f52fe15f04f74Antti Palosaari	hx->data[0] = 0;
1152347e6836ad2a5a2f7e62bd12b8f52fe15f04f74Antti Palosaari	ret = usb_cypress_writemem(udev, cypress[type].cs_reg, hx->data, 1);
1162347e6836ad2a5a2f7e62bd12b8f52fe15f04f74Antti Palosaari	if (ret != 1) {
1172347e6836ad2a5a2f7e62bd12b8f52fe15f04f74Antti Palosaari		dev_err(&udev->dev, "%s: CPU start failed=%d\n",
1182347e6836ad2a5a2f7e62bd12b8f52fe15f04f74Antti Palosaari				KBUILD_MODNAME, ret);
119b00a901801f671a48feac6048faeafe0979760e6Antti Palosaari		ret = -EIO;
1202347e6836ad2a5a2f7e62bd12b8f52fe15f04f74Antti Palosaari		goto err_kfree;
1212347e6836ad2a5a2f7e62bd12b8f52fe15f04f74Antti Palosaari	}
122b00a901801f671a48feac6048faeafe0979760e6Antti Palosaari
1232347e6836ad2a5a2f7e62bd12b8f52fe15f04f74Antti Palosaari	ret = 0;
1242347e6836ad2a5a2f7e62bd12b8f52fe15f04f74Antti Palosaarierr_kfree:
1252347e6836ad2a5a2f7e62bd12b8f52fe15f04f74Antti Palosaari	kfree(hx);
126b00a901801f671a48feac6048faeafe0979760e6Antti Palosaari	return ret;
127b00a901801f671a48feac6048faeafe0979760e6Antti Palosaari}
12879a63c60a6a2ae589e44529401e0ab1150e9408aHans VerkuilEXPORT_SYMBOL(cypress_load_firmware);
129b00a901801f671a48feac6048faeafe0979760e6Antti Palosaari
130b00a901801f671a48feac6048faeafe0979760e6Antti PalosaariMODULE_AUTHOR("Antti Palosaari <crope@iki.fi>");
131b00a901801f671a48feac6048faeafe0979760e6Antti PalosaariMODULE_DESCRIPTION("Cypress firmware download");
132b00a901801f671a48feac6048faeafe0979760e6Antti PalosaariMODULE_LICENSE("GPL");
133