| /* |
| * Abilis Systems Single DVB-T Receiver |
| * Copyright (C) 2008 Pierrick Hascoet <pierrick.hascoet@abilis.com> |
| * Copyright (C) 2010 Devin Heitmueller <dheitmueller@kernellabs.com> |
| * |
| * 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, 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. |
| */ |
| #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(sizeof(*fw_pkt), GFP_KERNEL); |
| 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); |
| |
| /* 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; |
| } |