linux/drivers/net/phy/microchip_t1.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0
   2// Copyright (C) 2018 Microchip Technology
   3
   4#include <linux/kernel.h>
   5#include <linux/module.h>
   6#include <linux/delay.h>
   7#include <linux/mii.h>
   8#include <linux/phy.h>
   9
  10/* External Register Control Register */
  11#define LAN87XX_EXT_REG_CTL                     (0x14)
  12#define LAN87XX_EXT_REG_CTL_RD_CTL              (0x1000)
  13#define LAN87XX_EXT_REG_CTL_WR_CTL              (0x0800)
  14
  15/* External Register Read Data Register */
  16#define LAN87XX_EXT_REG_RD_DATA                 (0x15)
  17
  18/* External Register Write Data Register */
  19#define LAN87XX_EXT_REG_WR_DATA                 (0x16)
  20
  21/* Interrupt Source Register */
  22#define LAN87XX_INTERRUPT_SOURCE                (0x18)
  23
  24/* Interrupt Mask Register */
  25#define LAN87XX_INTERRUPT_MASK                  (0x19)
  26#define LAN87XX_MASK_LINK_UP                    (0x0004)
  27#define LAN87XX_MASK_LINK_DOWN                  (0x0002)
  28
  29/* phyaccess nested types */
  30#define PHYACC_ATTR_MODE_READ           0
  31#define PHYACC_ATTR_MODE_WRITE          1
  32#define PHYACC_ATTR_MODE_MODIFY         2
  33
  34#define PHYACC_ATTR_BANK_SMI            0
  35#define PHYACC_ATTR_BANK_MISC           1
  36#define PHYACC_ATTR_BANK_PCS            2
  37#define PHYACC_ATTR_BANK_AFE            3
  38#define PHYACC_ATTR_BANK_MAX            7
  39
  40#define DRIVER_AUTHOR   "Nisar Sayed <nisar.sayed@microchip.com>"
  41#define DRIVER_DESC     "Microchip LAN87XX T1 PHY driver"
  42
  43struct access_ereg_val {
  44        u8  mode;
  45        u8  bank;
  46        u8  offset;
  47        u16 val;
  48        u16 mask;
  49};
  50
  51static int access_ereg(struct phy_device *phydev, u8 mode, u8 bank,
  52                       u8 offset, u16 val)
  53{
  54        u16 ereg = 0;
  55        int rc = 0;
  56
  57        if (mode > PHYACC_ATTR_MODE_WRITE || bank > PHYACC_ATTR_BANK_MAX)
  58                return -EINVAL;
  59
  60        if (bank == PHYACC_ATTR_BANK_SMI) {
  61                if (mode == PHYACC_ATTR_MODE_WRITE)
  62                        rc = phy_write(phydev, offset, val);
  63                else
  64                        rc = phy_read(phydev, offset);
  65                return rc;
  66        }
  67
  68        if (mode == PHYACC_ATTR_MODE_WRITE) {
  69                ereg = LAN87XX_EXT_REG_CTL_WR_CTL;
  70                rc = phy_write(phydev, LAN87XX_EXT_REG_WR_DATA, val);
  71                if (rc < 0)
  72                        return rc;
  73        } else {
  74                ereg = LAN87XX_EXT_REG_CTL_RD_CTL;
  75        }
  76
  77        ereg |= (bank << 8) | offset;
  78
  79        rc = phy_write(phydev, LAN87XX_EXT_REG_CTL, ereg);
  80        if (rc < 0)
  81                return rc;
  82
  83        if (mode == PHYACC_ATTR_MODE_READ)
  84                rc = phy_read(phydev, LAN87XX_EXT_REG_RD_DATA);
  85
  86        return rc;
  87}
  88
  89static int access_ereg_modify_changed(struct phy_device *phydev,
  90                                      u8 bank, u8 offset, u16 val, u16 mask)
  91{
  92        int new = 0, rc = 0;
  93
  94        if (bank > PHYACC_ATTR_BANK_MAX)
  95                return -EINVAL;
  96
  97        rc = access_ereg(phydev, PHYACC_ATTR_MODE_READ, bank, offset, val);
  98        if (rc < 0)
  99                return rc;
 100
 101        new = val | (rc & (mask ^ 0xFFFF));
 102        rc = access_ereg(phydev, PHYACC_ATTR_MODE_WRITE, bank, offset, new);
 103
 104        return rc;
 105}
 106
 107static int lan87xx_phy_init(struct phy_device *phydev)
 108{
 109        static const struct access_ereg_val init[] = {
 110                /* TX Amplitude = 5 */
 111                {PHYACC_ATTR_MODE_MODIFY, PHYACC_ATTR_BANK_AFE, 0x0B,
 112                 0x000A, 0x001E},
 113                /* Clear SMI interrupts */
 114                {PHYACC_ATTR_MODE_READ, PHYACC_ATTR_BANK_SMI, 0x18,
 115                 0, 0},
 116                /* Clear MISC interrupts */
 117                {PHYACC_ATTR_MODE_READ, PHYACC_ATTR_BANK_MISC, 0x08,
 118                 0, 0},
 119                /* Turn on TC10 Ring Oscillator (ROSC) */
 120                {PHYACC_ATTR_MODE_MODIFY, PHYACC_ATTR_BANK_MISC, 0x20,
 121                 0x0020, 0x0020},
 122                /* WUR Detect Length to 1.2uS, LPC Detect Length to 1.09uS */
 123                {PHYACC_ATTR_MODE_WRITE, PHYACC_ATTR_BANK_PCS, 0x20,
 124                 0x283C, 0},
 125                /* Wake_In Debounce Length to 39uS, Wake_Out Length to 79uS */
 126                {PHYACC_ATTR_MODE_WRITE, PHYACC_ATTR_BANK_MISC, 0x21,
 127                 0x274F, 0},
 128                /* Enable Auto Wake Forward to Wake_Out, ROSC on, Sleep,
 129                 * and Wake_In to wake PHY
 130                 */
 131                {PHYACC_ATTR_MODE_WRITE, PHYACC_ATTR_BANK_MISC, 0x20,
 132                 0x80A7, 0},
 133                /* Enable WUP Auto Fwd, Enable Wake on MDI, Wakeup Debouncer
 134                 * to 128 uS
 135                 */
 136                {PHYACC_ATTR_MODE_WRITE, PHYACC_ATTR_BANK_MISC, 0x24,
 137                 0xF110, 0},
 138                /* Enable HW Init */
 139                {PHYACC_ATTR_MODE_MODIFY, PHYACC_ATTR_BANK_SMI, 0x1A,
 140                 0x0100, 0x0100},
 141        };
 142        int rc, i;
 143
 144        /* Start manual initialization procedures in Managed Mode */
 145        rc = access_ereg_modify_changed(phydev, PHYACC_ATTR_BANK_SMI,
 146                                        0x1a, 0x0000, 0x0100);
 147        if (rc < 0)
 148                return rc;
 149
 150        /* Soft Reset the SMI block */
 151        rc = access_ereg_modify_changed(phydev, PHYACC_ATTR_BANK_SMI,
 152                                        0x00, 0x8000, 0x8000);
 153        if (rc < 0)
 154                return rc;
 155
 156        /* Check to see if the self-clearing bit is cleared */
 157        usleep_range(1000, 2000);
 158        rc = access_ereg(phydev, PHYACC_ATTR_MODE_READ,
 159                         PHYACC_ATTR_BANK_SMI, 0x00, 0);
 160        if (rc < 0)
 161                return rc;
 162        if ((rc & 0x8000) != 0)
 163                return -ETIMEDOUT;
 164
 165        /* PHY Initialization */
 166        for (i = 0; i < ARRAY_SIZE(init); i++) {
 167                if (init[i].mode == PHYACC_ATTR_MODE_MODIFY) {
 168                        rc = access_ereg_modify_changed(phydev, init[i].bank,
 169                                                        init[i].offset,
 170                                                        init[i].val,
 171                                                        init[i].mask);
 172                } else {
 173                        rc = access_ereg(phydev, init[i].mode, init[i].bank,
 174                                         init[i].offset, init[i].val);
 175                }
 176                if (rc < 0)
 177                        return rc;
 178        }
 179
 180        return 0;
 181}
 182
 183static int lan87xx_phy_config_intr(struct phy_device *phydev)
 184{
 185        int rc, val = 0;
 186
 187        if (phydev->interrupts == PHY_INTERRUPT_ENABLED) {
 188                /* unmask all source and clear them before enable */
 189                rc = phy_write(phydev, LAN87XX_INTERRUPT_MASK, 0x7FFF);
 190                rc = phy_read(phydev, LAN87XX_INTERRUPT_SOURCE);
 191                val = LAN87XX_MASK_LINK_UP | LAN87XX_MASK_LINK_DOWN;
 192                rc = phy_write(phydev, LAN87XX_INTERRUPT_MASK, val);
 193        } else {
 194                rc = phy_write(phydev, LAN87XX_INTERRUPT_MASK, val);
 195                if (rc)
 196                        return rc;
 197
 198                rc = phy_read(phydev, LAN87XX_INTERRUPT_SOURCE);
 199        }
 200
 201        return rc < 0 ? rc : 0;
 202}
 203
 204static irqreturn_t lan87xx_handle_interrupt(struct phy_device *phydev)
 205{
 206        int irq_status;
 207
 208        irq_status = phy_read(phydev, LAN87XX_INTERRUPT_SOURCE);
 209        if (irq_status < 0) {
 210                phy_error(phydev);
 211                return IRQ_NONE;
 212        }
 213
 214        if (irq_status == 0)
 215                return IRQ_NONE;
 216
 217        phy_trigger_machine(phydev);
 218
 219        return IRQ_HANDLED;
 220}
 221
 222static int lan87xx_config_init(struct phy_device *phydev)
 223{
 224        int rc = lan87xx_phy_init(phydev);
 225
 226        return rc < 0 ? rc : 0;
 227}
 228
 229static struct phy_driver microchip_t1_phy_driver[] = {
 230        {
 231                .phy_id         = 0x0007c150,
 232                .phy_id_mask    = 0xfffffff0,
 233                .name           = "Microchip LAN87xx T1",
 234
 235                .features       = PHY_BASIC_T1_FEATURES,
 236
 237                .config_init    = lan87xx_config_init,
 238
 239                .config_intr    = lan87xx_phy_config_intr,
 240                .handle_interrupt = lan87xx_handle_interrupt,
 241
 242                .suspend        = genphy_suspend,
 243                .resume         = genphy_resume,
 244        }
 245};
 246
 247module_phy_driver(microchip_t1_phy_driver);
 248
 249static struct mdio_device_id __maybe_unused microchip_t1_tbl[] = {
 250        { 0x0007c150, 0xfffffff0 },
 251        { }
 252};
 253
 254MODULE_DEVICE_TABLE(mdio, microchip_t1_tbl);
 255
 256MODULE_AUTHOR(DRIVER_AUTHOR);
 257MODULE_DESCRIPTION(DRIVER_DESC);
 258MODULE_LICENSE("GPL");
 259