This is the smi2021-bootloader module.
This module will upload the firmware for the different somagic devices.

Signed-off-by: Jon Arne Jørgensen <jona...@jonarne.no>
---
 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 majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Please read the FAQ at  http://www.tux.org/lkml/

Reply via email to