root/drivers/media/usb/as102/as102_fw.c
// SPDX-License-Identifier: GPL-2.0-or-later
/*
 * Abilis Systems Single DVB-T Receiver
 * Copyright (C) 2008 Pierrick Hascoet <pierrick.hascoet@abilis.com>
 * Copyright (C) 2010 Devin Heitmueller <dheitmueller@kernellabs.com>
 */
#include <linux/kernel.h>
#include <linux/errno.h>
#include <linux/ctype.h>
#include <linux/delay.h>
#include <linux/firmware.h>

#include "as102_drv.h"
#include "as102_fw.h"

static const char as102_st_fw1[] = "as102_data1_st.hex";
static const char as102_st_fw2[] = "as102_data2_st.hex";
static const char as102_dt_fw1[] = "as102_data1_dt.hex";
static const char as102_dt_fw2[] = "as102_data2_dt.hex";

static unsigned char atohx(unsigned char *dst, char *src)
{
        unsigned char value = 0;

        char msb = tolower(*src) - '0';
        char lsb = tolower(*(src + 1)) - '0';

        if (msb > 9)
                msb -= 7;
        if (lsb > 9)
                lsb -= 7;

        *dst = value = ((msb & 0xF) << 4) | (lsb & 0xF);
        return value;
}

/*
 * Parse INTEL HEX firmware file to extract address and data.
 */
static int parse_hex_line(unsigned char *fw_data, unsigned char *addr,
                          unsigned char *data, int *dataLength,
                          unsigned char *addr_has_changed) {

        int count = 0;
        unsigned char *src, dst;

        if (*fw_data++ != ':') {
                pr_err("invalid firmware file\n");
                return -EFAULT;
        }

        /* locate end of line */
        for (src = fw_data; *src != '\n'; src += 2) {
                atohx(&dst, src);
                /* parse line to split addr / data */
                switch (count) {
                case 0:
                        *dataLength = dst;
                        break;
                case 1:
                        addr[2] = dst;
                        break;
                case 2:
                        addr[3] = dst;
                        break;
                case 3:
                        /* check if data is an address */
                        if (dst == 0x04)
                                *addr_has_changed = 1;
                        else
                                *addr_has_changed = 0;
                        break;
                case  4:
                case  5:
                        if (*addr_has_changed)
                                addr[(count - 4)] = dst;
                        else
                                data[(count - 4)] = dst;
                        break;
                default:
                        data[(count - 4)] = dst;
                        break;
                }
                count++;
        }

        /* return read value + ':' + '\n' */
        return (count * 2) + 2;
}

static int as102_firmware_upload(struct as10x_bus_adapter_t *bus_adap,
                                 unsigned char *cmd,
                                 const struct firmware *firmware) {

        struct as10x_fw_pkt_t *fw_pkt;
        int total_read_bytes = 0, errno = 0;
        unsigned char addr_has_changed = 0;

        fw_pkt = kmalloc_obj(*fw_pkt);
        if (!fw_pkt)
                return -ENOMEM;


        for (total_read_bytes = 0; total_read_bytes < firmware->size; ) {
                int read_bytes = 0, data_len = 0;

                /* parse intel hex line */
                read_bytes = parse_hex_line(
                                (u8 *) (firmware->data + total_read_bytes),
                                fw_pkt->raw.address,
                                fw_pkt->raw.data,
                                &data_len,
                                &addr_has_changed);

                if (read_bytes <= 0)
                        goto error;

                /* detect the end of file */
                total_read_bytes += read_bytes;
                if (total_read_bytes == firmware->size) {
                        fw_pkt->u.request[0] = 0x00;
                        fw_pkt->u.request[1] = 0x03;

                        /* send EOF command */
                        errno = bus_adap->ops->upload_fw_pkt(bus_adap,
                                                             (uint8_t *)
                                                             fw_pkt, 2, 0);
                        if (errno < 0)
                                goto error;
                } else {
                        if (!addr_has_changed) {
                                /* prepare command to send */
                                fw_pkt->u.request[0] = 0x00;
                                fw_pkt->u.request[1] = 0x01;

                                data_len += sizeof(fw_pkt->u.request);
                                data_len += sizeof(fw_pkt->raw.address);

                                /* send cmd to device */
                                errno = bus_adap->ops->upload_fw_pkt(bus_adap,
                                                                     (uint8_t *)
                                                                     fw_pkt,
                                                                     data_len,
                                                                     0);
                                if (errno < 0)
                                        goto error;
                        }
                }
        }
error:
        kfree(fw_pkt);
        return (errno == 0) ? total_read_bytes : errno;
}

int as102_fw_upload(struct as10x_bus_adapter_t *bus_adap)
{
        int errno = -EFAULT;
        const struct firmware *firmware = NULL;
        unsigned char *cmd_buf = NULL;
        const char *fw1, *fw2;
        struct usb_device *dev = bus_adap->usb_dev;

        /* select fw file to upload */
        if (dual_tuner) {
                fw1 = as102_dt_fw1;
                fw2 = as102_dt_fw2;
        } else {
                fw1 = as102_st_fw1;
                fw2 = as102_st_fw2;
        }

        /* allocate buffer to store firmware upload command and data */
        cmd_buf = kzalloc(MAX_FW_PKT_SIZE, GFP_KERNEL);
        if (cmd_buf == NULL) {
                errno = -ENOMEM;
                goto error;
        }

        /* request kernel to locate firmware file: part1 */
        errno = request_firmware(&firmware, fw1, &dev->dev);
        if (errno < 0) {
                pr_err("%s: unable to locate firmware file: %s\n",
                       DRIVER_NAME, fw1);
                goto error;
        }

        /* initiate firmware upload */
        errno = as102_firmware_upload(bus_adap, cmd_buf, firmware);
        if (errno < 0) {
                pr_err("%s: error during firmware upload part1\n",
                       DRIVER_NAME);
                goto error;
        }

        pr_info("%s: firmware: %s loaded with success\n",
                DRIVER_NAME, fw1);
        release_firmware(firmware);
        firmware = NULL;

        /* wait for boot to complete */
        mdelay(100);

        /* request kernel to locate firmware file: part2 */
        errno = request_firmware(&firmware, fw2, &dev->dev);
        if (errno < 0) {
                pr_err("%s: unable to locate firmware file: %s\n",
                       DRIVER_NAME, fw2);
                goto error;
        }

        /* initiate firmware upload */
        errno = as102_firmware_upload(bus_adap, cmd_buf, firmware);
        if (errno < 0) {
                pr_err("%s: error during firmware upload part2\n",
                       DRIVER_NAME);
                goto error;
        }

        pr_info("%s: firmware: %s loaded with success\n",
                DRIVER_NAME, fw2);
error:
        kfree(cmd_buf);
        release_firmware(firmware);

        return errno;
}