[RFC V1 7/8] smi2021: Add smi2021_bl.c
From: Jon Arne JÃrgensen
Date: Thu Mar 14 2013 - 10:11:46 EST
This is the smi2021-bootloader module.
This module will upload the firmware for the different somagic devices.
Signed-off-by: Jon Arne JÃrgensen <jonarne@xxxxxxxxxx>
---
drivers/media/usb/smi2021/smi2021_bl.c | 254 +++++++++++++++++++++++++++++++++
1 file changed, 254 insertions(+)
create mode 100644 drivers/media/usb/smi2021/smi2021_bl.c
diff --git a/drivers/media/usb/smi2021/smi2021_bl.c b/drivers/media/usb/smi2021/smi2021_bl.c
new file mode 100644
index 0000000..025b06c
--- /dev/null
+++ b/drivers/media/usb/smi2021/smi2021_bl.c
@@ -0,0 +1,254 @@
+/*******************************************************************************
+ * smi2021_bl.c *
+ * *
+ * USB Driver for SMI2021 - EasyCAP *
+ * USB ID 1c88:003c *
+ * *
+ * *****************************************************************************
+ *
+ * Copyright 2011-2013 Jon Arne JÃrgensen
+ * <jonjon.arnearne--a.t--gmail.com>
+ *
+ * Copyright 2011, 2012 Tony Brown, Michal Demin, Jeffry Johnston
+ *
+ * This file is part of SMI2021
+ * http://code.google.com/p/easycap-somagic-linux/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * This driver is heavily influensed by the STK1160 driver.
+ * Copyright (C) 2012 Ezequiel Garcia
+ * <elezegarcia--a.t--gmail.com>
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/usb.h>
+#include <linux/firmware.h>
+#include <linux/slab.h>
+
+#define FIRMWARE_CHUNK_SIZE 62
+#define FIRMWARE_HEADER_SIZE 2
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Jon Arne JÃrgensen <jonjon.arnearne--a.t--gmail.com>");
+MODULE_DESCRIPTION("SMI2021 - Bootloader");
+MODULE_VERSION("0.1");
+
+static unsigned int firmware_version;
+module_param(firmware_version, int, 0644);
+MODULE_PARM_DESC(firmware_version,
+ "Firmware version to be uploaded to device\n"
+ "if there are more than one firmware present");
+
+struct usb_device_id smi2021_bootloader_id_table[] = {
+ { USB_DEVICE(0x1c88, 0x0007) },
+ { }
+};
+
+struct smi2021_firmware {
+ int id;
+ const char *name;
+ int found;
+};
+
+struct smi2021_firmware available_fw[] = {
+ {
+ .id = 0x3c,
+ .name = "smi2021_3c.bin",
+ },
+ {
+ .id = 0x3e,
+ .name = "smi2021_3e.bin",
+ },
+ {
+ .id = 0x3f,
+ .name = "smi2021_3f.bin",
+ }
+};
+
+static const struct firmware *firmware[ARRAY_SIZE(available_fw)];
+static int firmwares = -1;
+
+static int smi2021_load_firmware(struct usb_device *udev,
+ const struct firmware *firmware)
+{
+ int i, size, rc = 0;
+ u8 *chunk;
+ u16 ack = 0x0000;
+
+ if (udev == NULL)
+ goto end_out;
+
+ size = FIRMWARE_CHUNK_SIZE + FIRMWARE_HEADER_SIZE;
+ chunk = kzalloc(size, GFP_KERNEL);
+ chunk[0] = 0x05;
+ chunk[1] = 0xff;
+
+ if (chunk == NULL) {
+ dev_err(&udev->dev,
+ "could not allocate space for firmware chunk\n");
+ goto end_out;
+ }
+
+ if (firmware == NULL) {
+ dev_err(&udev->dev, "firmware is NULL\n");
+ rc = -ENODEV;
+ goto free_out;
+ }
+
+ if (firmware->size % FIRMWARE_CHUNK_SIZE) {
+ dev_err(&udev->dev, "firmware has wrong size\n");
+ rc = -ENODEV;
+ goto free_out;
+ }
+
+ rc = usb_control_msg(udev, usb_rcvctrlpipe(udev, 0x80), 0x01,
+ USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+ 0x0001, 0x0000, &ack, sizeof(ack), 1000);
+
+ if (rc < 0) {
+ dev_err(&udev->dev, "could not prepare device for upload: %d\n",
+ rc);
+ goto free_out;
+ }
+ if (__cpu_to_be16(ack) != 0x0107) {
+ dev_err(&udev->dev, "could not prepare device for upload: %d\n",
+ rc);
+ goto free_out;
+ }
+
+ for (i = 0; i < firmware->size / FIRMWARE_CHUNK_SIZE; i++) {
+ memcpy(chunk + FIRMWARE_HEADER_SIZE,
+ firmware->data + (i * FIRMWARE_CHUNK_SIZE),
+ FIRMWARE_CHUNK_SIZE);
+
+ rc = usb_control_msg(udev, usb_sndctrlpipe(udev, 0x00), 0x01,
+ USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+ 0x0005, 0x0000, chunk, size, 1000);
+ if (rc < 0) {
+ dev_err(&udev->dev, "firmware upload failed: %d\n",
+ rc);
+ goto free_out;
+ }
+ }
+
+ ack = __cpu_to_le16(0x0007);
+ rc = usb_control_msg(udev, usb_sndctrlpipe(udev, 0x00), 0x01,
+ USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+ 0x0007, 0x0000, &ack, sizeof(ack), 1000);
+
+ if (rc < 0) {
+ dev_err(&udev->dev, "device failed to ack firmware: %d\n", rc);
+ goto free_out;
+ }
+
+ rc = 0;
+
+free_out:
+ kfree(chunk);
+end_out:
+ return rc;
+}
+
+static int smi2021_choose_firmware(struct usb_device *udev)
+{
+ int i, found, id;
+ for (i = 0; i < ARRAY_SIZE(available_fw); i++) {
+ found = available_fw[i].found;
+ id = available_fw[i].id;
+ if (firmware_version == id && found >= 0) {
+ dev_info(&udev->dev, "uploading firmware for 0x%x\n",
+ id);
+ return smi2021_load_firmware(udev, firmware[found]);
+ }
+ }
+
+ dev_info(&udev->dev,
+ "could not decide what firmware to upload, user action required\n");
+ return 0;
+}
+
+static int smi2021_bootloader_probe(struct usb_interface *intf,
+ const struct usb_device_id *devid)
+{
+ struct usb_device *udev = interface_to_usbdev(intf);
+ int rc, i;
+
+ /* Check what firmwares are available in the system */
+ for (i = 0; i < ARRAY_SIZE(available_fw); i++) {
+ dev_info(&udev->dev, "Looking for: %s\n", available_fw[i].name);
+ rc = request_firmware(&firmware[firmwares + 1],
+ available_fw[i].name, &udev->dev);
+
+ if (rc == 0) {
+ firmwares++;
+ available_fw[i].found = firmwares;
+ dev_info(&udev->dev, "Found firmware for 0x00%x\n",
+ available_fw[i].id);
+ } else if (rc == -ENOENT) {
+ available_fw[i].found = -1;
+ } else {
+ dev_err(&udev->dev,
+ "request_firmware failed with: %d\n", rc);
+ goto err_out;
+ }
+ }
+
+ if (firmwares < 0) {
+ dev_err(&udev->dev,
+ "could not find any firmware for this device\n");
+ goto no_dev;
+ } else if (firmwares == 0) {
+ rc = smi2021_load_firmware(udev, firmware[0]);
+ if (rc < 0)
+ goto err_out;
+ } else {
+ smi2021_choose_firmware(udev);
+ }
+
+ return 0;
+
+no_dev:
+ rc = -ENODEV;
+err_out:
+ return rc;
+}
+
+static void smi2021_bootloader_disconnect(struct usb_interface *intf)
+{
+ struct usb_device *udev = interface_to_usbdev(intf);
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(available_fw); i++) {
+ if (available_fw[i].found >= 0) {
+ dev_info(&udev->dev, "Releasing firmware for 0x00%x\n",
+ available_fw[i].id);
+ release_firmware(firmware[available_fw[i].found]);
+ firmware[available_fw[i].found] = NULL;
+ available_fw[i].found = -1;
+ }
+ }
+ firmwares = -1;
+
+}
+
+struct usb_driver smi2021_bootloader_driver = {
+ .name = "smi2021_bootloader",
+ .id_table = smi2021_bootloader_id_table,
+ .probe = smi2021_bootloader_probe,
+ .disconnect = smi2021_bootloader_disconnect
+};
+
+module_usb_driver(smi2021_bootloader_driver);
--
1.8.1.1
--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/