| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088 |
- // SPDX-License-Identifier: GPL-2.0
- /*
- * Aeonsemi AS21XXxX PHY Driver
- *
- * Author: Christian Marangi <ansuelsmth@gmail.com>
- */
- #include <linux/bitfield.h>
- #include <linux/firmware.h>
- #include <linux/module.h>
- #include <linux/of.h>
- #include <linux/phy.h>
- #define VEND1_GLB_REG_CPU_RESET_ADDR_LO_BASEADDR 0x3
- #define VEND1_GLB_REG_CPU_RESET_ADDR_HI_BASEADDR 0x4
- #define VEND1_GLB_REG_CPU_CTRL 0xe
- #define VEND1_GLB_CPU_CTRL_MASK GENMASK(4, 0)
- #define VEND1_GLB_CPU_CTRL_LED_POLARITY_MASK GENMASK(12, 8)
- #define VEND1_GLB_CPU_CTRL_LED_POLARITY(_n) FIELD_PREP(VEND1_GLB_CPU_CTRL_LED_POLARITY_MASK, \
- BIT(_n))
- #define VEND1_FW_START_ADDR 0x100
- #define VEND1_GLB_REG_MDIO_INDIRECT_ADDRCMD 0x101
- #define VEND1_GLB_REG_MDIO_INDIRECT_LOAD 0x102
- #define VEND1_GLB_REG_MDIO_INDIRECT_STATUS 0x103
- #define VEND1_PTP_CLK 0x142
- #define VEND1_PTP_CLK_EN BIT(6)
- /* 5 LED at step of 0x20
- * FE: Fast-Ethernet (10/100)
- * GE: Gigabit-Ethernet (1000)
- * NG: New-Generation (2500/5000/10000)
- */
- #define VEND1_LED_REG(_n) (0x1800 + ((_n) * 0x10))
- #define VEND1_LED_REG_A_EVENT GENMASK(15, 11)
- #define VEND1_LED_CONF 0x1881
- #define VEND1_LED_CONFG_BLINK GENMASK(7, 0)
- #define VEND1_SPEED_STATUS 0x4002
- #define VEND1_SPEED_MASK GENMASK(7, 0)
- #define VEND1_SPEED_10000 FIELD_PREP_CONST(VEND1_SPEED_MASK, 0x3)
- #define VEND1_SPEED_5000 FIELD_PREP_CONST(VEND1_SPEED_MASK, 0x5)
- #define VEND1_SPEED_2500 FIELD_PREP_CONST(VEND1_SPEED_MASK, 0x9)
- #define VEND1_SPEED_1000 FIELD_PREP_CONST(VEND1_SPEED_MASK, 0x10)
- #define VEND1_SPEED_100 FIELD_PREP_CONST(VEND1_SPEED_MASK, 0x20)
- #define VEND1_SPEED_10 FIELD_PREP_CONST(VEND1_SPEED_MASK, 0x0)
- #define VEND1_IPC_CMD 0x5801
- #define AEON_IPC_CMD_PARITY BIT(15)
- #define AEON_IPC_CMD_SIZE GENMASK(10, 6)
- #define AEON_IPC_CMD_OPCODE GENMASK(5, 0)
- #define IPC_CMD_NOOP 0x0 /* Do nothing */
- #define IPC_CMD_INFO 0x1 /* Get Firmware Version */
- #define IPC_CMD_SYS_CPU 0x2 /* SYS_CPU */
- #define IPC_CMD_BULK_DATA 0xa /* Pass bulk data in ipc registers. */
- #define IPC_CMD_BULK_WRITE 0xc /* Write bulk data to memory */
- #define IPC_CMD_CFG_PARAM 0x1a /* Write config parameters to memory */
- #define IPC_CMD_NG_TESTMODE 0x1b /* Set NG test mode and tone */
- #define IPC_CMD_TEMP_MON 0x15 /* Temperature monitoring function */
- #define IPC_CMD_SET_LED 0x23 /* Set led */
- #define VEND1_IPC_STS 0x5802
- #define AEON_IPC_STS_PARITY BIT(15)
- #define AEON_IPC_STS_SIZE GENMASK(14, 10)
- #define AEON_IPC_STS_OPCODE GENMASK(9, 4)
- #define AEON_IPC_STS_STATUS GENMASK(3, 0)
- #define AEON_IPC_STS_STATUS_RCVD FIELD_PREP_CONST(AEON_IPC_STS_STATUS, 0x1)
- #define AEON_IPC_STS_STATUS_PROCESS FIELD_PREP_CONST(AEON_IPC_STS_STATUS, 0x2)
- #define AEON_IPC_STS_STATUS_SUCCESS FIELD_PREP_CONST(AEON_IPC_STS_STATUS, 0x4)
- #define AEON_IPC_STS_STATUS_ERROR FIELD_PREP_CONST(AEON_IPC_STS_STATUS, 0x8)
- #define AEON_IPC_STS_STATUS_BUSY FIELD_PREP_CONST(AEON_IPC_STS_STATUS, 0xe)
- #define AEON_IPC_STS_STATUS_READY FIELD_PREP_CONST(AEON_IPC_STS_STATUS, 0xf)
- #define VEND1_IPC_DATA0 0x5808
- #define VEND1_IPC_DATA1 0x5809
- #define VEND1_IPC_DATA2 0x580a
- #define VEND1_IPC_DATA3 0x580b
- #define VEND1_IPC_DATA4 0x580c
- #define VEND1_IPC_DATA5 0x580d
- #define VEND1_IPC_DATA6 0x580e
- #define VEND1_IPC_DATA7 0x580f
- #define VEND1_IPC_DATA(_n) (VEND1_IPC_DATA0 + (_n))
- /* Sub command of CMD_INFO */
- #define IPC_INFO_VERSION 0x1
- /* Sub command of CMD_SYS_CPU */
- #define IPC_SYS_CPU_REBOOT 0x3
- #define IPC_SYS_CPU_IMAGE_OFST 0x4
- #define IPC_SYS_CPU_IMAGE_CHECK 0x5
- #define IPC_SYS_CPU_PHY_ENABLE 0x6
- /* Sub command of CMD_CFG_PARAM */
- #define IPC_CFG_PARAM_DIRECT 0x4
- /* CFG DIRECT sub command */
- #define IPC_CFG_PARAM_DIRECT_NG_PHYCTRL 0x1
- #define IPC_CFG_PARAM_DIRECT_CU_AN 0x2
- #define IPC_CFG_PARAM_DIRECT_SDS_PCS 0x3
- #define IPC_CFG_PARAM_DIRECT_AUTO_EEE 0x4
- #define IPC_CFG_PARAM_DIRECT_SDS_PMA 0x5
- #define IPC_CFG_PARAM_DIRECT_DPC_RA 0x6
- #define IPC_CFG_PARAM_DIRECT_DPC_PKT_CHK 0x7
- #define IPC_CFG_PARAM_DIRECT_DPC_SDS_WAIT_ETH 0x8
- #define IPC_CFG_PARAM_DIRECT_WDT 0x9
- #define IPC_CFG_PARAM_DIRECT_SDS_RESTART_AN 0x10
- #define IPC_CFG_PARAM_DIRECT_TEMP_MON 0x11
- #define IPC_CFG_PARAM_DIRECT_WOL 0x12
- /* Sub command of CMD_TEMP_MON */
- #define IPC_CMD_TEMP_MON_GET 0x4
- #define AS21XXX_MDIO_AN_C22 0xffe0
- #define PHY_ID_AS21XXX 0x75009410
- /* AS21xxx ID Legend
- * AS21x1xxB1
- * ^ ^^
- * | |J: Supports SyncE/PTP
- * | |P: No SyncE/PTP support
- * | 1: Supports 2nd Serdes
- * | 2: Not 2nd Serdes support
- * 0: 10G, 5G, 2.5G
- * 5: 5G, 2.5G
- * 2: 2.5G
- */
- #define PHY_ID_AS21011JB1 0x75009402
- #define PHY_ID_AS21011PB1 0x75009412
- #define PHY_ID_AS21010JB1 0x75009422
- #define PHY_ID_AS21010PB1 0x75009432
- #define PHY_ID_AS21511JB1 0x75009442
- #define PHY_ID_AS21511PB1 0x75009452
- #define PHY_ID_AS21510JB1 0x75009462
- #define PHY_ID_AS21510PB1 0x75009472
- #define PHY_ID_AS21210JB1 0x75009482
- #define PHY_ID_AS21210PB1 0x75009492
- #define PHY_VENDOR_AEONSEMI 0x75009400
- #define AEON_MAX_LEDS 5
- #define AEON_IPC_DELAY 10000
- #define AEON_IPC_TIMEOUT (AEON_IPC_DELAY * 100)
- #define AEON_IPC_DATA_NUM_REGISTERS 8
- #define AEON_IPC_DATA_MAX (AEON_IPC_DATA_NUM_REGISTERS * sizeof(u16))
- #define AEON_BOOT_ADDR 0x1000
- #define AEON_CPU_BOOT_ADDR 0x2000
- #define AEON_CPU_CTRL_FW_LOAD (BIT(4) | BIT(2) | BIT(1) | BIT(0))
- #define AEON_CPU_CTRL_FW_START BIT(0)
- enum as21xxx_led_event {
- VEND1_LED_REG_A_EVENT_ON_10 = 0x0,
- VEND1_LED_REG_A_EVENT_ON_100,
- VEND1_LED_REG_A_EVENT_ON_1000,
- VEND1_LED_REG_A_EVENT_ON_2500,
- VEND1_LED_REG_A_EVENT_ON_5000,
- VEND1_LED_REG_A_EVENT_ON_10000,
- VEND1_LED_REG_A_EVENT_ON_FE_GE,
- VEND1_LED_REG_A_EVENT_ON_NG,
- VEND1_LED_REG_A_EVENT_ON_FULL_DUPLEX,
- VEND1_LED_REG_A_EVENT_ON_COLLISION,
- VEND1_LED_REG_A_EVENT_BLINK_TX,
- VEND1_LED_REG_A_EVENT_BLINK_RX,
- VEND1_LED_REG_A_EVENT_BLINK_ACT,
- VEND1_LED_REG_A_EVENT_ON_LINK,
- VEND1_LED_REG_A_EVENT_ON_LINK_BLINK_ACT,
- VEND1_LED_REG_A_EVENT_ON_LINK_BLINK_RX,
- VEND1_LED_REG_A_EVENT_ON_FE_GE_BLINK_ACT,
- VEND1_LED_REG_A_EVENT_ON_NG_BLINK_ACT,
- VEND1_LED_REG_A_EVENT_ON_NG_BLINK_FE_GE,
- VEND1_LED_REG_A_EVENT_ON_FD_BLINK_COLLISION,
- VEND1_LED_REG_A_EVENT_ON,
- VEND1_LED_REG_A_EVENT_OFF,
- };
- struct as21xxx_led_pattern_info {
- unsigned int pattern;
- u16 val;
- };
- struct as21xxx_priv {
- bool parity_status;
- /* Protect concurrent IPC access */
- struct mutex ipc_lock;
- };
- static struct as21xxx_led_pattern_info as21xxx_led_supported_pattern[] = {
- {
- .pattern = BIT(TRIGGER_NETDEV_LINK_10),
- .val = VEND1_LED_REG_A_EVENT_ON_10
- },
- {
- .pattern = BIT(TRIGGER_NETDEV_LINK_100),
- .val = VEND1_LED_REG_A_EVENT_ON_100
- },
- {
- .pattern = BIT(TRIGGER_NETDEV_LINK_1000),
- .val = VEND1_LED_REG_A_EVENT_ON_1000
- },
- {
- .pattern = BIT(TRIGGER_NETDEV_LINK_2500),
- .val = VEND1_LED_REG_A_EVENT_ON_2500
- },
- {
- .pattern = BIT(TRIGGER_NETDEV_LINK_5000),
- .val = VEND1_LED_REG_A_EVENT_ON_5000
- },
- {
- .pattern = BIT(TRIGGER_NETDEV_LINK_10000),
- .val = VEND1_LED_REG_A_EVENT_ON_10000
- },
- {
- .pattern = BIT(TRIGGER_NETDEV_LINK),
- .val = VEND1_LED_REG_A_EVENT_ON_LINK
- },
- {
- .pattern = BIT(TRIGGER_NETDEV_LINK_10) |
- BIT(TRIGGER_NETDEV_LINK_100) |
- BIT(TRIGGER_NETDEV_LINK_1000),
- .val = VEND1_LED_REG_A_EVENT_ON_FE_GE
- },
- {
- .pattern = BIT(TRIGGER_NETDEV_LINK_2500) |
- BIT(TRIGGER_NETDEV_LINK_5000) |
- BIT(TRIGGER_NETDEV_LINK_10000),
- .val = VEND1_LED_REG_A_EVENT_ON_NG
- },
- {
- .pattern = BIT(TRIGGER_NETDEV_FULL_DUPLEX),
- .val = VEND1_LED_REG_A_EVENT_ON_FULL_DUPLEX
- },
- {
- .pattern = BIT(TRIGGER_NETDEV_TX),
- .val = VEND1_LED_REG_A_EVENT_BLINK_TX
- },
- {
- .pattern = BIT(TRIGGER_NETDEV_RX),
- .val = VEND1_LED_REG_A_EVENT_BLINK_RX
- },
- {
- .pattern = BIT(TRIGGER_NETDEV_TX) |
- BIT(TRIGGER_NETDEV_RX),
- .val = VEND1_LED_REG_A_EVENT_BLINK_ACT
- },
- {
- .pattern = BIT(TRIGGER_NETDEV_LINK_10) |
- BIT(TRIGGER_NETDEV_LINK_100) |
- BIT(TRIGGER_NETDEV_LINK_1000) |
- BIT(TRIGGER_NETDEV_LINK_2500) |
- BIT(TRIGGER_NETDEV_LINK_5000) |
- BIT(TRIGGER_NETDEV_LINK_10000),
- .val = VEND1_LED_REG_A_EVENT_ON_LINK
- },
- {
- .pattern = BIT(TRIGGER_NETDEV_LINK_10) |
- BIT(TRIGGER_NETDEV_LINK_100) |
- BIT(TRIGGER_NETDEV_LINK_1000) |
- BIT(TRIGGER_NETDEV_LINK_2500) |
- BIT(TRIGGER_NETDEV_LINK_5000) |
- BIT(TRIGGER_NETDEV_LINK_10000) |
- BIT(TRIGGER_NETDEV_TX) |
- BIT(TRIGGER_NETDEV_RX),
- .val = VEND1_LED_REG_A_EVENT_ON_LINK_BLINK_ACT
- },
- {
- .pattern = BIT(TRIGGER_NETDEV_LINK_10) |
- BIT(TRIGGER_NETDEV_LINK_100) |
- BIT(TRIGGER_NETDEV_LINK_1000) |
- BIT(TRIGGER_NETDEV_LINK_2500) |
- BIT(TRIGGER_NETDEV_LINK_5000) |
- BIT(TRIGGER_NETDEV_LINK_10000) |
- BIT(TRIGGER_NETDEV_RX),
- .val = VEND1_LED_REG_A_EVENT_ON_LINK_BLINK_RX
- },
- {
- .pattern = BIT(TRIGGER_NETDEV_LINK_10) |
- BIT(TRIGGER_NETDEV_LINK_100) |
- BIT(TRIGGER_NETDEV_LINK_1000) |
- BIT(TRIGGER_NETDEV_TX) |
- BIT(TRIGGER_NETDEV_RX),
- .val = VEND1_LED_REG_A_EVENT_ON_FE_GE_BLINK_ACT
- },
- {
- .pattern = BIT(TRIGGER_NETDEV_LINK_2500) |
- BIT(TRIGGER_NETDEV_LINK_5000) |
- BIT(TRIGGER_NETDEV_LINK_10000) |
- BIT(TRIGGER_NETDEV_TX) |
- BIT(TRIGGER_NETDEV_RX),
- .val = VEND1_LED_REG_A_EVENT_ON_NG_BLINK_ACT
- }
- };
- static int aeon_firmware_boot(struct phy_device *phydev, const u8 *data,
- size_t size)
- {
- int i, ret;
- u16 val;
- ret = phy_modify_mmd(phydev, MDIO_MMD_VEND1, VEND1_GLB_REG_CPU_CTRL,
- VEND1_GLB_CPU_CTRL_MASK, AEON_CPU_CTRL_FW_LOAD);
- if (ret)
- return ret;
- ret = phy_write_mmd(phydev, MDIO_MMD_VEND1, VEND1_FW_START_ADDR,
- AEON_BOOT_ADDR);
- if (ret)
- return ret;
- ret = phy_modify_mmd(phydev, MDIO_MMD_VEND1,
- VEND1_GLB_REG_MDIO_INDIRECT_ADDRCMD,
- 0x3ffc, 0xc000);
- if (ret)
- return ret;
- val = phy_read_mmd(phydev, MDIO_MMD_VEND1,
- VEND1_GLB_REG_MDIO_INDIRECT_STATUS);
- if (val > 1) {
- phydev_err(phydev, "wrong origin mdio_indirect_status: %x\n", val);
- return -EINVAL;
- }
- /* Firmware is always aligned to u16 */
- for (i = 0; i < size; i += 2) {
- val = data[i + 1] << 8 | data[i];
- ret = phy_write_mmd(phydev, MDIO_MMD_VEND1,
- VEND1_GLB_REG_MDIO_INDIRECT_LOAD, val);
- if (ret)
- return ret;
- }
- ret = phy_write_mmd(phydev, MDIO_MMD_VEND1,
- VEND1_GLB_REG_CPU_RESET_ADDR_LO_BASEADDR,
- lower_16_bits(AEON_CPU_BOOT_ADDR));
- if (ret)
- return ret;
- ret = phy_write_mmd(phydev, MDIO_MMD_VEND1,
- VEND1_GLB_REG_CPU_RESET_ADDR_HI_BASEADDR,
- upper_16_bits(AEON_CPU_BOOT_ADDR));
- if (ret)
- return ret;
- return phy_modify_mmd(phydev, MDIO_MMD_VEND1, VEND1_GLB_REG_CPU_CTRL,
- VEND1_GLB_CPU_CTRL_MASK, AEON_CPU_CTRL_FW_START);
- }
- static int aeon_firmware_load(struct phy_device *phydev)
- {
- struct device *dev = &phydev->mdio.dev;
- const struct firmware *fw;
- const char *fw_name;
- int ret;
- ret = of_property_read_string(dev->of_node, "firmware-name",
- &fw_name);
- if (ret)
- return ret;
- ret = request_firmware(&fw, fw_name, dev);
- if (ret) {
- phydev_err(phydev, "failed to find FW file %s (%d)\n",
- fw_name, ret);
- return ret;
- }
- ret = aeon_firmware_boot(phydev, fw->data, fw->size);
- release_firmware(fw);
- return ret;
- }
- static bool aeon_ipc_ready(u16 val, bool parity_status)
- {
- u16 status;
- if (FIELD_GET(AEON_IPC_STS_PARITY, val) != parity_status)
- return false;
- status = val & AEON_IPC_STS_STATUS;
- return status != AEON_IPC_STS_STATUS_RCVD &&
- status != AEON_IPC_STS_STATUS_PROCESS &&
- status != AEON_IPC_STS_STATUS_BUSY;
- }
- static int aeon_ipc_wait_cmd(struct phy_device *phydev, bool parity_status)
- {
- u16 val;
- /* Exit condition logic:
- * - Wait for parity bit equal
- * - Wait for status success, error OR ready
- */
- return phy_read_mmd_poll_timeout(phydev, MDIO_MMD_VEND1, VEND1_IPC_STS, val,
- aeon_ipc_ready(val, parity_status),
- AEON_IPC_DELAY, AEON_IPC_TIMEOUT, false);
- }
- static int aeon_ipc_send_cmd(struct phy_device *phydev,
- struct as21xxx_priv *priv,
- u16 cmd, u16 *ret_sts)
- {
- bool curr_parity;
- int ret;
- /* The IPC sync by using a single parity bit.
- * Each CMD have alternately this bit set or clear
- * to understand correct flow and packet order.
- */
- curr_parity = priv->parity_status;
- if (priv->parity_status)
- cmd |= AEON_IPC_CMD_PARITY;
- /* Always update parity for next packet */
- priv->parity_status = !priv->parity_status;
- ret = phy_write_mmd(phydev, MDIO_MMD_VEND1, VEND1_IPC_CMD, cmd);
- if (ret)
- return ret;
- /* Wait for packet to be processed */
- usleep_range(AEON_IPC_DELAY, AEON_IPC_DELAY + 5000);
- /* With no ret_sts, ignore waiting for packet completion
- * (ipc parity bit sync)
- */
- if (!ret_sts)
- return 0;
- ret = aeon_ipc_wait_cmd(phydev, curr_parity);
- if (ret)
- return ret;
- ret = phy_read_mmd(phydev, MDIO_MMD_VEND1, VEND1_IPC_STS);
- if (ret < 0)
- return ret;
- *ret_sts = ret;
- if ((*ret_sts & AEON_IPC_STS_STATUS) != AEON_IPC_STS_STATUS_SUCCESS)
- return -EINVAL;
- return 0;
- }
- /* If data is NULL, return 0 or negative error.
- * If data not NULL, return number of Bytes received from IPC or
- * a negative error.
- */
- static int aeon_ipc_send_msg(struct phy_device *phydev,
- u16 opcode, u16 *data, unsigned int data_len,
- u16 *ret_data)
- {
- struct as21xxx_priv *priv = phydev->priv;
- unsigned int ret_size;
- u16 cmd, ret_sts;
- int ret;
- int i;
- /* IPC have a max of 8 register to transfer data,
- * make sure we never exceed this.
- */
- if (data_len > AEON_IPC_DATA_MAX)
- return -EINVAL;
- for (i = 0; i < data_len / sizeof(u16); i++)
- phy_write_mmd(phydev, MDIO_MMD_VEND1, VEND1_IPC_DATA(i),
- data[i]);
- cmd = FIELD_PREP(AEON_IPC_CMD_SIZE, data_len) |
- FIELD_PREP(AEON_IPC_CMD_OPCODE, opcode);
- mutex_lock(&priv->ipc_lock);
- ret = aeon_ipc_send_cmd(phydev, priv, cmd, &ret_sts);
- if (ret) {
- phydev_err(phydev, "failed to send ipc msg for %x: %d\n",
- opcode, ret);
- goto out;
- }
- if (!data)
- goto out;
- if ((ret_sts & AEON_IPC_STS_STATUS) == AEON_IPC_STS_STATUS_ERROR) {
- ret = -EINVAL;
- goto out;
- }
- /* Prevent IPC from stack smashing the kernel.
- * We can't trust IPC to return a good value and we always
- * preallocate space for 16 Bytes.
- */
- ret_size = FIELD_GET(AEON_IPC_STS_SIZE, ret_sts);
- if (ret_size > AEON_IPC_DATA_MAX) {
- ret = -EINVAL;
- goto out;
- }
- /* Read data from IPC data register for ret_size value from IPC */
- for (i = 0; i < DIV_ROUND_UP(ret_size, sizeof(u16)); i++) {
- ret = phy_read_mmd(phydev, MDIO_MMD_VEND1, VEND1_IPC_DATA(i));
- if (ret < 0)
- goto out;
- ret_data[i] = ret;
- }
- ret = ret_size;
- out:
- mutex_unlock(&priv->ipc_lock);
- return ret;
- }
- static int aeon_ipc_noop(struct phy_device *phydev,
- struct as21xxx_priv *priv, u16 *ret_sts)
- {
- u16 cmd;
- cmd = FIELD_PREP(AEON_IPC_CMD_SIZE, 0) |
- FIELD_PREP(AEON_IPC_CMD_OPCODE, IPC_CMD_NOOP);
- return aeon_ipc_send_cmd(phydev, priv, cmd, ret_sts);
- }
- /* Logic to sync parity bit with IPC.
- * We send 2 NOP cmd with same partity and we wait for IPC
- * to handle the packet only for the second one. This way
- * we make sure we are sync for every next cmd.
- */
- static int aeon_ipc_sync_parity(struct phy_device *phydev,
- struct as21xxx_priv *priv)
- {
- u16 ret_sts;
- int ret;
- mutex_lock(&priv->ipc_lock);
- /* Send NOP with no parity */
- aeon_ipc_noop(phydev, priv, NULL);
- /* Reset packet parity */
- priv->parity_status = false;
- /* Send second NOP with no parity */
- ret = aeon_ipc_noop(phydev, priv, &ret_sts);
- mutex_unlock(&priv->ipc_lock);
- /* We expect to return -EINVAL */
- if (ret != -EINVAL)
- return ret;
- if ((ret_sts & AEON_IPC_STS_STATUS) != AEON_IPC_STS_STATUS_READY) {
- phydev_err(phydev, "Invalid IPC status on sync parity: %x\n",
- ret_sts);
- return -EINVAL;
- }
- return 0;
- }
- static int aeon_ipc_get_fw_version(struct phy_device *phydev)
- {
- u16 ret_data[AEON_IPC_DATA_NUM_REGISTERS], data[1];
- char fw_version[AEON_IPC_DATA_MAX + 1];
- int ret;
- data[0] = IPC_INFO_VERSION;
- ret = aeon_ipc_send_msg(phydev, IPC_CMD_INFO, data,
- sizeof(data), ret_data);
- if (ret < 0)
- return ret;
- /* Make sure FW version is NULL terminated */
- memcpy(fw_version, ret_data, ret);
- fw_version[ret] = '\0';
- phydev_info(phydev, "Firmware Version: %s\n", fw_version);
- return 0;
- }
- static int aeon_dpc_ra_enable(struct phy_device *phydev)
- {
- u16 data[2];
- data[0] = IPC_CFG_PARAM_DIRECT;
- data[1] = IPC_CFG_PARAM_DIRECT_DPC_RA;
- return aeon_ipc_send_msg(phydev, IPC_CMD_CFG_PARAM, data,
- sizeof(data), NULL);
- }
- static int as21xxx_probe(struct phy_device *phydev)
- {
- struct as21xxx_priv *priv;
- int ret;
- priv = devm_kzalloc(&phydev->mdio.dev,
- sizeof(*priv), GFP_KERNEL);
- if (!priv)
- return -ENOMEM;
- phydev->priv = priv;
- ret = devm_mutex_init(&phydev->mdio.dev,
- &priv->ipc_lock);
- if (ret)
- return ret;
- ret = aeon_ipc_sync_parity(phydev, priv);
- if (ret)
- return ret;
- ret = aeon_ipc_get_fw_version(phydev);
- if (ret)
- return ret;
- /* Enable PTP clk if not already Enabled */
- ret = phy_set_bits_mmd(phydev, MDIO_MMD_VEND1, VEND1_PTP_CLK,
- VEND1_PTP_CLK_EN);
- if (ret)
- return ret;
- return aeon_dpc_ra_enable(phydev);
- }
- static int as21xxx_read_link(struct phy_device *phydev, int *bmcr)
- {
- int status;
- /* Normal C22 BMCR report inconsistent data, use
- * the mapped C22 in C45 to have more consistent link info.
- */
- *bmcr = phy_read_mmd(phydev, MDIO_MMD_AN,
- AS21XXX_MDIO_AN_C22 + MII_BMCR);
- if (*bmcr < 0)
- return *bmcr;
- /* Autoneg is being started, therefore disregard current
- * link status and report link as down.
- */
- if (*bmcr & BMCR_ANRESTART) {
- phydev->link = 0;
- return 0;
- }
- status = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_STAT1);
- if (status < 0)
- return status;
- phydev->link = !!(status & MDIO_STAT1_LSTATUS);
- return 0;
- }
- static int as21xxx_read_c22_lpa(struct phy_device *phydev)
- {
- int lpagb;
- /* MII_STAT1000 are only filled in the mapped C22
- * in C45, use that to fill lpagb values and check.
- */
- lpagb = phy_read_mmd(phydev, MDIO_MMD_AN,
- AS21XXX_MDIO_AN_C22 + MII_STAT1000);
- if (lpagb < 0)
- return lpagb;
- if (lpagb & LPA_1000MSFAIL) {
- int adv = phy_read_mmd(phydev, MDIO_MMD_AN,
- AS21XXX_MDIO_AN_C22 + MII_CTRL1000);
- if (adv < 0)
- return adv;
- if (adv & CTL1000_ENABLE_MASTER)
- phydev_err(phydev, "Master/Slave resolution failed, maybe conflicting manual settings?\n");
- else
- phydev_err(phydev, "Master/Slave resolution failed\n");
- return -ENOLINK;
- }
- mii_stat1000_mod_linkmode_lpa_t(phydev->lp_advertising,
- lpagb);
- return 0;
- }
- static int as21xxx_read_status(struct phy_device *phydev)
- {
- int bmcr, old_link = phydev->link;
- int ret;
- ret = as21xxx_read_link(phydev, &bmcr);
- if (ret)
- return ret;
- /* why bother the PHY if nothing can have changed */
- if (phydev->autoneg == AUTONEG_ENABLE && old_link && phydev->link)
- return 0;
- phydev->speed = SPEED_UNKNOWN;
- phydev->duplex = DUPLEX_UNKNOWN;
- phydev->pause = 0;
- phydev->asym_pause = 0;
- if (phydev->autoneg == AUTONEG_ENABLE) {
- ret = genphy_c45_read_lpa(phydev);
- if (ret)
- return ret;
- ret = as21xxx_read_c22_lpa(phydev);
- if (ret)
- return ret;
- phy_resolve_aneg_linkmode(phydev);
- } else {
- int speed;
- linkmode_zero(phydev->lp_advertising);
- speed = phy_read_mmd(phydev, MDIO_MMD_VEND1,
- VEND1_SPEED_STATUS);
- if (speed < 0)
- return speed;
- switch (speed & VEND1_SPEED_STATUS) {
- case VEND1_SPEED_10000:
- phydev->speed = SPEED_10000;
- phydev->duplex = DUPLEX_FULL;
- break;
- case VEND1_SPEED_5000:
- phydev->speed = SPEED_5000;
- phydev->duplex = DUPLEX_FULL;
- break;
- case VEND1_SPEED_2500:
- phydev->speed = SPEED_2500;
- phydev->duplex = DUPLEX_FULL;
- break;
- case VEND1_SPEED_1000:
- phydev->speed = SPEED_1000;
- if (bmcr & BMCR_FULLDPLX)
- phydev->duplex = DUPLEX_FULL;
- else
- phydev->duplex = DUPLEX_HALF;
- break;
- case VEND1_SPEED_100:
- phydev->speed = SPEED_100;
- phydev->duplex = DUPLEX_FULL;
- break;
- case VEND1_SPEED_10:
- phydev->speed = SPEED_10;
- phydev->duplex = DUPLEX_FULL;
- break;
- default:
- return -EINVAL;
- }
- }
- return 0;
- }
- static int as21xxx_led_brightness_set(struct phy_device *phydev,
- u8 index, enum led_brightness value)
- {
- u16 val = VEND1_LED_REG_A_EVENT_OFF;
- if (index > AEON_MAX_LEDS)
- return -EINVAL;
- if (value)
- val = VEND1_LED_REG_A_EVENT_ON;
- return phy_modify_mmd(phydev, MDIO_MMD_VEND1,
- VEND1_LED_REG(index),
- VEND1_LED_REG_A_EVENT,
- FIELD_PREP(VEND1_LED_REG_A_EVENT, val));
- }
- static int as21xxx_led_hw_is_supported(struct phy_device *phydev, u8 index,
- unsigned long rules)
- {
- int i;
- if (index > AEON_MAX_LEDS)
- return -EINVAL;
- for (i = 0; i < ARRAY_SIZE(as21xxx_led_supported_pattern); i++)
- if (rules == as21xxx_led_supported_pattern[i].pattern)
- return 0;
- return -EOPNOTSUPP;
- }
- static int as21xxx_led_hw_control_get(struct phy_device *phydev, u8 index,
- unsigned long *rules)
- {
- int i, val;
- if (index > AEON_MAX_LEDS)
- return -EINVAL;
- val = phy_read_mmd(phydev, MDIO_MMD_VEND1, VEND1_LED_REG(index));
- if (val < 0)
- return val;
- val = FIELD_GET(VEND1_LED_REG_A_EVENT, val);
- for (i = 0; i < ARRAY_SIZE(as21xxx_led_supported_pattern); i++)
- if (val == as21xxx_led_supported_pattern[i].val) {
- *rules = as21xxx_led_supported_pattern[i].pattern;
- return 0;
- }
- /* Should be impossible */
- return -EINVAL;
- }
- static int as21xxx_led_hw_control_set(struct phy_device *phydev, u8 index,
- unsigned long rules)
- {
- u16 val = 0;
- int i;
- if (index > AEON_MAX_LEDS)
- return -EINVAL;
- for (i = 0; i < ARRAY_SIZE(as21xxx_led_supported_pattern); i++)
- if (rules == as21xxx_led_supported_pattern[i].pattern) {
- val = as21xxx_led_supported_pattern[i].val;
- break;
- }
- return phy_modify_mmd(phydev, MDIO_MMD_VEND1,
- VEND1_LED_REG(index),
- VEND1_LED_REG_A_EVENT,
- FIELD_PREP(VEND1_LED_REG_A_EVENT, val));
- }
- static int as21xxx_led_polarity_set(struct phy_device *phydev, int index,
- unsigned long modes)
- {
- bool led_active_low = false;
- u16 mask, val = 0;
- u32 mode;
- if (index > AEON_MAX_LEDS)
- return -EINVAL;
- for_each_set_bit(mode, &modes, __PHY_LED_MODES_NUM) {
- switch (mode) {
- case PHY_LED_ACTIVE_LOW:
- led_active_low = true;
- break;
- case PHY_LED_ACTIVE_HIGH: /* default mode */
- led_active_low = false;
- break;
- default:
- return -EINVAL;
- }
- }
- mask = VEND1_GLB_CPU_CTRL_LED_POLARITY(index);
- if (led_active_low)
- val = VEND1_GLB_CPU_CTRL_LED_POLARITY(index);
- return phy_modify_mmd(phydev, MDIO_MMD_VEND1,
- VEND1_GLB_REG_CPU_CTRL,
- mask, val);
- }
- static int as21xxx_match_phy_device(struct phy_device *phydev,
- const struct phy_driver *phydrv)
- {
- struct as21xxx_priv *priv;
- u16 ret_sts;
- u32 phy_id;
- int ret;
- /* Skip PHY that are not AS21xxx */
- if (!phy_id_compare_vendor(phydev->c45_ids.device_ids[MDIO_MMD_PCS],
- PHY_VENDOR_AEONSEMI))
- return genphy_match_phy_device(phydev, phydrv);
- /* Read PHY ID to handle firmware loaded or HW reset */
- ret = phy_read_mmd(phydev, MDIO_MMD_PCS, MII_PHYSID1);
- if (ret < 0)
- return ret;
- phy_id = ret << 16;
- ret = phy_read_mmd(phydev, MDIO_MMD_PCS, MII_PHYSID2);
- if (ret < 0)
- return ret;
- phy_id |= ret;
- /* With PHY ID not the generic AS21xxx one assume
- * the firmware just loaded
- */
- if (phy_id != PHY_ID_AS21XXX)
- return phy_id == phydrv->phy_id;
- /* Allocate temp priv and load the firmware */
- priv = kzalloc_obj(*priv);
- if (!priv)
- return -ENOMEM;
- mutex_init(&priv->ipc_lock);
- ret = aeon_firmware_load(phydev);
- if (ret)
- goto out;
- /* Sync parity... */
- ret = aeon_ipc_sync_parity(phydev, priv);
- if (ret)
- goto out;
- /* ...and send a third NOOP cmd to wait for firmware finish loading */
- ret = aeon_ipc_noop(phydev, priv, &ret_sts);
- if (ret)
- goto out;
- out:
- mutex_destroy(&priv->ipc_lock);
- kfree(priv);
- /* Return can either be 0 or a negative error code.
- * Returning 0 here means THIS is NOT a suitable PHY.
- *
- * For the specific case of the generic Aeonsemi PHY ID that
- * needs the firmware the be loaded first to have a correct PHY ID,
- * this is OK as a matching PHY ID will be found right after.
- * This relies on the driver probe order where the first PHY driver
- * probed is the generic one.
- */
- return ret;
- }
- static struct phy_driver as21xxx_drivers[] = {
- {
- /* PHY expose in C45 as 0x7500 0x9410
- * before firmware is loaded.
- * This driver entry must be attempted first to load
- * the firmware and thus update the ID registers.
- */
- PHY_ID_MATCH_EXACT(PHY_ID_AS21XXX),
- .name = "Aeonsemi AS21xxx",
- .match_phy_device = as21xxx_match_phy_device,
- },
- {
- PHY_ID_MATCH_EXACT(PHY_ID_AS21011JB1),
- .name = "Aeonsemi AS21011JB1",
- .probe = as21xxx_probe,
- .match_phy_device = as21xxx_match_phy_device,
- .read_status = as21xxx_read_status,
- .led_brightness_set = as21xxx_led_brightness_set,
- .led_hw_is_supported = as21xxx_led_hw_is_supported,
- .led_hw_control_set = as21xxx_led_hw_control_set,
- .led_hw_control_get = as21xxx_led_hw_control_get,
- .led_polarity_set = as21xxx_led_polarity_set,
- },
- {
- PHY_ID_MATCH_EXACT(PHY_ID_AS21011PB1),
- .name = "Aeonsemi AS21011PB1",
- .probe = as21xxx_probe,
- .match_phy_device = as21xxx_match_phy_device,
- .read_status = as21xxx_read_status,
- .led_brightness_set = as21xxx_led_brightness_set,
- .led_hw_is_supported = as21xxx_led_hw_is_supported,
- .led_hw_control_set = as21xxx_led_hw_control_set,
- .led_hw_control_get = as21xxx_led_hw_control_get,
- .led_polarity_set = as21xxx_led_polarity_set,
- },
- {
- PHY_ID_MATCH_EXACT(PHY_ID_AS21010PB1),
- .name = "Aeonsemi AS21010PB1",
- .probe = as21xxx_probe,
- .match_phy_device = as21xxx_match_phy_device,
- .read_status = as21xxx_read_status,
- .led_brightness_set = as21xxx_led_brightness_set,
- .led_hw_is_supported = as21xxx_led_hw_is_supported,
- .led_hw_control_set = as21xxx_led_hw_control_set,
- .led_hw_control_get = as21xxx_led_hw_control_get,
- .led_polarity_set = as21xxx_led_polarity_set,
- },
- {
- PHY_ID_MATCH_EXACT(PHY_ID_AS21010JB1),
- .name = "Aeonsemi AS21010JB1",
- .probe = as21xxx_probe,
- .match_phy_device = as21xxx_match_phy_device,
- .read_status = as21xxx_read_status,
- .led_brightness_set = as21xxx_led_brightness_set,
- .led_hw_is_supported = as21xxx_led_hw_is_supported,
- .led_hw_control_set = as21xxx_led_hw_control_set,
- .led_hw_control_get = as21xxx_led_hw_control_get,
- .led_polarity_set = as21xxx_led_polarity_set,
- },
- {
- PHY_ID_MATCH_EXACT(PHY_ID_AS21210PB1),
- .name = "Aeonsemi AS21210PB1",
- .probe = as21xxx_probe,
- .match_phy_device = as21xxx_match_phy_device,
- .read_status = as21xxx_read_status,
- .led_brightness_set = as21xxx_led_brightness_set,
- .led_hw_is_supported = as21xxx_led_hw_is_supported,
- .led_hw_control_set = as21xxx_led_hw_control_set,
- .led_hw_control_get = as21xxx_led_hw_control_get,
- .led_polarity_set = as21xxx_led_polarity_set,
- },
- {
- PHY_ID_MATCH_EXACT(PHY_ID_AS21510JB1),
- .name = "Aeonsemi AS21510JB1",
- .probe = as21xxx_probe,
- .match_phy_device = as21xxx_match_phy_device,
- .read_status = as21xxx_read_status,
- .led_brightness_set = as21xxx_led_brightness_set,
- .led_hw_is_supported = as21xxx_led_hw_is_supported,
- .led_hw_control_set = as21xxx_led_hw_control_set,
- .led_hw_control_get = as21xxx_led_hw_control_get,
- .led_polarity_set = as21xxx_led_polarity_set,
- },
- {
- PHY_ID_MATCH_EXACT(PHY_ID_AS21510PB1),
- .name = "Aeonsemi AS21510PB1",
- .probe = as21xxx_probe,
- .match_phy_device = as21xxx_match_phy_device,
- .read_status = as21xxx_read_status,
- .led_brightness_set = as21xxx_led_brightness_set,
- .led_hw_is_supported = as21xxx_led_hw_is_supported,
- .led_hw_control_set = as21xxx_led_hw_control_set,
- .led_hw_control_get = as21xxx_led_hw_control_get,
- .led_polarity_set = as21xxx_led_polarity_set,
- },
- {
- PHY_ID_MATCH_EXACT(PHY_ID_AS21511JB1),
- .name = "Aeonsemi AS21511JB1",
- .probe = as21xxx_probe,
- .match_phy_device = as21xxx_match_phy_device,
- .read_status = as21xxx_read_status,
- .led_brightness_set = as21xxx_led_brightness_set,
- .led_hw_is_supported = as21xxx_led_hw_is_supported,
- .led_hw_control_set = as21xxx_led_hw_control_set,
- .led_hw_control_get = as21xxx_led_hw_control_get,
- .led_polarity_set = as21xxx_led_polarity_set,
- },
- {
- PHY_ID_MATCH_EXACT(PHY_ID_AS21210JB1),
- .name = "Aeonsemi AS21210JB1",
- .probe = as21xxx_probe,
- .match_phy_device = as21xxx_match_phy_device,
- .read_status = as21xxx_read_status,
- .led_brightness_set = as21xxx_led_brightness_set,
- .led_hw_is_supported = as21xxx_led_hw_is_supported,
- .led_hw_control_set = as21xxx_led_hw_control_set,
- .led_hw_control_get = as21xxx_led_hw_control_get,
- .led_polarity_set = as21xxx_led_polarity_set,
- },
- {
- PHY_ID_MATCH_EXACT(PHY_ID_AS21511PB1),
- .name = "Aeonsemi AS21511PB1",
- .probe = as21xxx_probe,
- .match_phy_device = as21xxx_match_phy_device,
- .read_status = as21xxx_read_status,
- .led_brightness_set = as21xxx_led_brightness_set,
- .led_hw_is_supported = as21xxx_led_hw_is_supported,
- .led_hw_control_set = as21xxx_led_hw_control_set,
- .led_hw_control_get = as21xxx_led_hw_control_get,
- .led_polarity_set = as21xxx_led_polarity_set,
- },
- };
- module_phy_driver(as21xxx_drivers);
- static struct mdio_device_id __maybe_unused as21xxx_tbl[] = {
- { PHY_ID_MATCH_VENDOR(PHY_VENDOR_AEONSEMI) },
- { }
- };
- MODULE_DEVICE_TABLE(mdio, as21xxx_tbl);
- MODULE_DESCRIPTION("Aeonsemi AS21xxx PHY driver");
- MODULE_AUTHOR("Christian Marangi <ansuelsmth@gmail.com>");
- MODULE_LICENSE("GPL");
|