mirror of https://github.com/Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
236 lines
4.8 KiB
236 lines
4.8 KiB
// SPDX-License-Identifier: GPL-2.0-only |
|
/* r8169_firmware.c: RealTek 8169/8168/8101 ethernet driver. |
|
* |
|
* Copyright (c) 2002 ShuChen <[email protected]> |
|
* Copyright (c) 2003 - 2007 Francois Romieu <[email protected]> |
|
* Copyright (c) a lot of people too. Please respect their work. |
|
* |
|
* See MAINTAINERS file for support contact information. |
|
*/ |
|
|
|
#include <linux/delay.h> |
|
#include <linux/firmware.h> |
|
|
|
#include "r8169_firmware.h" |
|
|
|
enum rtl_fw_opcode { |
|
PHY_READ = 0x0, |
|
PHY_DATA_OR = 0x1, |
|
PHY_DATA_AND = 0x2, |
|
PHY_BJMPN = 0x3, |
|
PHY_MDIO_CHG = 0x4, |
|
PHY_CLEAR_READCOUNT = 0x7, |
|
PHY_WRITE = 0x8, |
|
PHY_READCOUNT_EQ_SKIP = 0x9, |
|
PHY_COMP_EQ_SKIPN = 0xa, |
|
PHY_COMP_NEQ_SKIPN = 0xb, |
|
PHY_WRITE_PREVIOUS = 0xc, |
|
PHY_SKIPN = 0xd, |
|
PHY_DELAY_MS = 0xe, |
|
}; |
|
|
|
struct fw_info { |
|
u32 magic; |
|
char version[RTL_VER_SIZE]; |
|
__le32 fw_start; |
|
__le32 fw_len; |
|
u8 chksum; |
|
} __packed; |
|
|
|
#define FW_OPCODE_SIZE sizeof_field(struct rtl_fw_phy_action, code[0]) |
|
|
|
static bool rtl_fw_format_ok(struct rtl_fw *rtl_fw) |
|
{ |
|
const struct firmware *fw = rtl_fw->fw; |
|
struct fw_info *fw_info = (struct fw_info *)fw->data; |
|
struct rtl_fw_phy_action *pa = &rtl_fw->phy_action; |
|
|
|
if (fw->size < FW_OPCODE_SIZE) |
|
return false; |
|
|
|
if (!fw_info->magic) { |
|
size_t i, size, start; |
|
u8 checksum = 0; |
|
|
|
if (fw->size < sizeof(*fw_info)) |
|
return false; |
|
|
|
for (i = 0; i < fw->size; i++) |
|
checksum += fw->data[i]; |
|
if (checksum != 0) |
|
return false; |
|
|
|
start = le32_to_cpu(fw_info->fw_start); |
|
if (start > fw->size) |
|
return false; |
|
|
|
size = le32_to_cpu(fw_info->fw_len); |
|
if (size > (fw->size - start) / FW_OPCODE_SIZE) |
|
return false; |
|
|
|
strscpy(rtl_fw->version, fw_info->version, RTL_VER_SIZE); |
|
|
|
pa->code = (__le32 *)(fw->data + start); |
|
pa->size = size; |
|
} else { |
|
if (fw->size % FW_OPCODE_SIZE) |
|
return false; |
|
|
|
strscpy(rtl_fw->version, rtl_fw->fw_name, RTL_VER_SIZE); |
|
|
|
pa->code = (__le32 *)fw->data; |
|
pa->size = fw->size / FW_OPCODE_SIZE; |
|
} |
|
|
|
return true; |
|
} |
|
|
|
static bool rtl_fw_data_ok(struct rtl_fw *rtl_fw) |
|
{ |
|
struct rtl_fw_phy_action *pa = &rtl_fw->phy_action; |
|
size_t index; |
|
|
|
for (index = 0; index < pa->size; index++) { |
|
u32 action = le32_to_cpu(pa->code[index]); |
|
u32 val = action & 0x0000ffff; |
|
u32 regno = (action & 0x0fff0000) >> 16; |
|
|
|
switch (action >> 28) { |
|
case PHY_READ: |
|
case PHY_DATA_OR: |
|
case PHY_DATA_AND: |
|
case PHY_CLEAR_READCOUNT: |
|
case PHY_WRITE: |
|
case PHY_WRITE_PREVIOUS: |
|
case PHY_DELAY_MS: |
|
break; |
|
|
|
case PHY_MDIO_CHG: |
|
if (val > 1) |
|
goto out; |
|
break; |
|
|
|
case PHY_BJMPN: |
|
if (regno > index) |
|
goto out; |
|
break; |
|
case PHY_READCOUNT_EQ_SKIP: |
|
if (index + 2 >= pa->size) |
|
goto out; |
|
break; |
|
case PHY_COMP_EQ_SKIPN: |
|
case PHY_COMP_NEQ_SKIPN: |
|
case PHY_SKIPN: |
|
if (index + 1 + regno >= pa->size) |
|
goto out; |
|
break; |
|
|
|
default: |
|
dev_err(rtl_fw->dev, "Invalid action 0x%08x\n", action); |
|
return false; |
|
} |
|
} |
|
|
|
return true; |
|
out: |
|
dev_err(rtl_fw->dev, "Out of range of firmware\n"); |
|
return false; |
|
} |
|
|
|
void rtl_fw_write_firmware(struct rtl8169_private *tp, struct rtl_fw *rtl_fw) |
|
{ |
|
struct rtl_fw_phy_action *pa = &rtl_fw->phy_action; |
|
rtl_fw_write_t fw_write = rtl_fw->phy_write; |
|
rtl_fw_read_t fw_read = rtl_fw->phy_read; |
|
int predata = 0, count = 0; |
|
size_t index; |
|
|
|
for (index = 0; index < pa->size; index++) { |
|
u32 action = le32_to_cpu(pa->code[index]); |
|
u32 data = action & 0x0000ffff; |
|
u32 regno = (action & 0x0fff0000) >> 16; |
|
enum rtl_fw_opcode opcode = action >> 28; |
|
|
|
if (!action) |
|
break; |
|
|
|
switch (opcode) { |
|
case PHY_READ: |
|
predata = fw_read(tp, regno); |
|
count++; |
|
break; |
|
case PHY_DATA_OR: |
|
predata |= data; |
|
break; |
|
case PHY_DATA_AND: |
|
predata &= data; |
|
break; |
|
case PHY_BJMPN: |
|
index -= (regno + 1); |
|
break; |
|
case PHY_MDIO_CHG: |
|
if (data) { |
|
fw_write = rtl_fw->mac_mcu_write; |
|
fw_read = rtl_fw->mac_mcu_read; |
|
} else { |
|
fw_write = rtl_fw->phy_write; |
|
fw_read = rtl_fw->phy_read; |
|
} |
|
|
|
break; |
|
case PHY_CLEAR_READCOUNT: |
|
count = 0; |
|
break; |
|
case PHY_WRITE: |
|
fw_write(tp, regno, data); |
|
break; |
|
case PHY_READCOUNT_EQ_SKIP: |
|
if (count == data) |
|
index++; |
|
break; |
|
case PHY_COMP_EQ_SKIPN: |
|
if (predata == data) |
|
index += regno; |
|
break; |
|
case PHY_COMP_NEQ_SKIPN: |
|
if (predata != data) |
|
index += regno; |
|
break; |
|
case PHY_WRITE_PREVIOUS: |
|
fw_write(tp, regno, predata); |
|
break; |
|
case PHY_SKIPN: |
|
index += regno; |
|
break; |
|
case PHY_DELAY_MS: |
|
msleep(data); |
|
break; |
|
} |
|
} |
|
} |
|
|
|
void rtl_fw_release_firmware(struct rtl_fw *rtl_fw) |
|
{ |
|
release_firmware(rtl_fw->fw); |
|
} |
|
|
|
int rtl_fw_request_firmware(struct rtl_fw *rtl_fw) |
|
{ |
|
int rc; |
|
|
|
rc = request_firmware(&rtl_fw->fw, rtl_fw->fw_name, rtl_fw->dev); |
|
if (rc < 0) |
|
goto out; |
|
|
|
if (!rtl_fw_format_ok(rtl_fw) || !rtl_fw_data_ok(rtl_fw)) { |
|
release_firmware(rtl_fw->fw); |
|
rc = -EINVAL; |
|
goto out; |
|
} |
|
|
|
return 0; |
|
out: |
|
dev_err(rtl_fw->dev, "Unable to load firmware %s (%d)\n", |
|
rtl_fw->fw_name, rc); |
|
return rc; |
|
}
|
|
|