1// SPDX-License-Identifier: GPL-2.0+ 2/* 3 * Qualcomm IPQ4019 MDIO driver 4 * 5 * Copyright (c) 2020 Sartura Ltd. 6 * 7 * Author: Luka Kovacic <luka.kovacic@sartura.hr> 8 * Author: Robert Marko <robert.marko@sartura.hr> 9 * 10 * Based on Linux driver 11 */ 12 13#include <asm/io.h> 14#include <common.h> 15#include <dm.h> 16#include <errno.h> 17#include <linux/bitops.h> 18#include <linux/iopoll.h> 19#include <miiphy.h> 20#include <phy.h> 21 22#define MDIO_MODE_REG 0x40 23#define MDIO_ADDR_REG 0x44 24#define MDIO_DATA_WRITE_REG 0x48 25#define MDIO_DATA_READ_REG 0x4c 26#define MDIO_CMD_REG 0x50 27#define MDIO_CMD_ACCESS_BUSY BIT(16) 28#define MDIO_CMD_ACCESS_START BIT(8) 29#define MDIO_CMD_ACCESS_CODE_READ 0 30#define MDIO_CMD_ACCESS_CODE_WRITE 1 31 32/* 0 = Clause 22, 1 = Clause 45 */ 33#define MDIO_MODE_BIT BIT(8) 34 35#define IPQ4019_MDIO_TIMEOUT 10000 36#define IPQ4019_MDIO_SLEEP 10 37 38struct ipq4019_mdio_priv { 39 phys_addr_t mdio_base; 40}; 41 42static int ipq4019_mdio_wait_busy(struct ipq4019_mdio_priv *priv) 43{ 44 unsigned int busy; 45 46 return readl_poll_sleep_timeout(priv->mdio_base + MDIO_CMD_REG, busy, 47 (busy & MDIO_CMD_ACCESS_BUSY) == 0, IPQ4019_MDIO_SLEEP, 48 IPQ4019_MDIO_TIMEOUT); 49} 50 51int ipq4019_mdio_read(struct udevice *dev, int addr, int devad, int reg) 52{ 53 struct ipq4019_mdio_priv *priv = dev_get_priv(dev); 54 unsigned int cmd; 55 56 if (ipq4019_mdio_wait_busy(priv)) 57 return -ETIMEDOUT; 58 59 /* Issue the phy address and reg */ 60 writel((addr << 8) | reg, priv->mdio_base + MDIO_ADDR_REG); 61 62 cmd = MDIO_CMD_ACCESS_START | MDIO_CMD_ACCESS_CODE_READ; 63 64 /* Issue read command */ 65 writel(cmd, priv->mdio_base + MDIO_CMD_REG); 66 67 /* Wait read complete */ 68 if (ipq4019_mdio_wait_busy(priv)) 69 return -ETIMEDOUT; 70 71 /* Read and return data */ 72 return readl(priv->mdio_base + MDIO_DATA_READ_REG); 73} 74 75int ipq4019_mdio_write(struct udevice *dev, int addr, int devad, 76 int reg, u16 val) 77{ 78 struct ipq4019_mdio_priv *priv = dev_get_priv(dev); 79 unsigned int cmd; 80 81 if (ipq4019_mdio_wait_busy(priv)) 82 return -ETIMEDOUT; 83 84 /* Issue the phy addreass and reg */ 85 writel((addr << 8) | reg, priv->mdio_base + MDIO_ADDR_REG); 86 87 /* Issue write data */ 88 writel(val, priv->mdio_base + MDIO_DATA_WRITE_REG); 89 90 cmd = MDIO_CMD_ACCESS_START | MDIO_CMD_ACCESS_CODE_WRITE; 91 92 /* Issue write command */ 93 writel(cmd, priv->mdio_base + MDIO_CMD_REG); 94 95 /* Wait for write complete */ 96 97 if (ipq4019_mdio_wait_busy(priv)) 98 return -ETIMEDOUT; 99 100 return 0; 101} 102 103static const struct mdio_ops ipq4019_mdio_ops = { 104 .read = ipq4019_mdio_read, 105 .write = ipq4019_mdio_write, 106}; 107 108static int ipq4019_mdio_bind(struct udevice *dev) 109{ 110 if (ofnode_valid(dev_ofnode(dev))) 111 device_set_name(dev, ofnode_get_name(dev_ofnode(dev))); 112 113 return 0; 114} 115 116static int ipq4019_mdio_probe(struct udevice *dev) 117{ 118 struct ipq4019_mdio_priv *priv = dev_get_priv(dev); 119 unsigned int data; 120 121 priv->mdio_base = dev_read_addr(dev); 122 if (priv->mdio_base == FDT_ADDR_T_NONE) 123 return -EINVAL; 124 125 /* Enter Clause 22 mode */ 126 data = readl(priv->mdio_base + MDIO_MODE_REG); 127 data &= ~MDIO_MODE_BIT; 128 writel(data, priv->mdio_base + MDIO_MODE_REG); 129 130 return 0; 131} 132 133static const struct udevice_id ipq4019_mdio_ids[] = { 134 { .compatible = "qcom,ipq4019-mdio", }, 135 { } 136}; 137 138U_BOOT_DRIVER(ipq4019_mdio) = { 139 .name = "ipq4019_mdio", 140 .id = UCLASS_MDIO, 141 .of_match = ipq4019_mdio_ids, 142 .bind = ipq4019_mdio_bind, 143 .probe = ipq4019_mdio_probe, 144 .ops = &ipq4019_mdio_ops, 145 .priv_auto = sizeof(struct ipq4019_mdio_priv), 146}; 147