davicom.c (5052B)
1// SPDX-License-Identifier: GPL-2.0+ 2/* 3 * drivers/net/phy/davicom.c 4 * 5 * Driver for Davicom PHYs 6 * 7 * Author: Andy Fleming 8 * 9 * Copyright (c) 2004 Freescale Semiconductor, Inc. 10 */ 11#include <linux/kernel.h> 12#include <linux/string.h> 13#include <linux/errno.h> 14#include <linux/unistd.h> 15#include <linux/interrupt.h> 16#include <linux/init.h> 17#include <linux/delay.h> 18#include <linux/netdevice.h> 19#include <linux/etherdevice.h> 20#include <linux/skbuff.h> 21#include <linux/spinlock.h> 22#include <linux/mm.h> 23#include <linux/module.h> 24#include <linux/mii.h> 25#include <linux/ethtool.h> 26#include <linux/phy.h> 27 28#include <asm/io.h> 29#include <asm/irq.h> 30#include <linux/uaccess.h> 31 32#define MII_DM9161_SCR 0x10 33#define MII_DM9161_SCR_INIT 0x0610 34#define MII_DM9161_SCR_RMII 0x0100 35 36/* DM9161 Interrupt Register */ 37#define MII_DM9161_INTR 0x15 38#define MII_DM9161_INTR_PEND 0x8000 39#define MII_DM9161_INTR_DPLX_MASK 0x0800 40#define MII_DM9161_INTR_SPD_MASK 0x0400 41#define MII_DM9161_INTR_LINK_MASK 0x0200 42#define MII_DM9161_INTR_MASK 0x0100 43#define MII_DM9161_INTR_DPLX_CHANGE 0x0010 44#define MII_DM9161_INTR_SPD_CHANGE 0x0008 45#define MII_DM9161_INTR_LINK_CHANGE 0x0004 46#define MII_DM9161_INTR_INIT 0x0000 47#define MII_DM9161_INTR_STOP \ 48 (MII_DM9161_INTR_DPLX_MASK | MII_DM9161_INTR_SPD_MASK | \ 49 MII_DM9161_INTR_LINK_MASK | MII_DM9161_INTR_MASK) 50#define MII_DM9161_INTR_CHANGE \ 51 (MII_DM9161_INTR_DPLX_CHANGE | \ 52 MII_DM9161_INTR_SPD_CHANGE | \ 53 MII_DM9161_INTR_LINK_CHANGE) 54 55/* DM9161 10BT Configuration/Status */ 56#define MII_DM9161_10BTCSR 0x12 57#define MII_DM9161_10BTCSR_INIT 0x7800 58 59MODULE_DESCRIPTION("Davicom PHY driver"); 60MODULE_AUTHOR("Andy Fleming"); 61MODULE_LICENSE("GPL"); 62 63 64static int dm9161_ack_interrupt(struct phy_device *phydev) 65{ 66 int err = phy_read(phydev, MII_DM9161_INTR); 67 68 return (err < 0) ? err : 0; 69} 70 71#define DM9161_DELAY 1 72static int dm9161_config_intr(struct phy_device *phydev) 73{ 74 int temp, err; 75 76 temp = phy_read(phydev, MII_DM9161_INTR); 77 78 if (temp < 0) 79 return temp; 80 81 if (phydev->interrupts == PHY_INTERRUPT_ENABLED) { 82 err = dm9161_ack_interrupt(phydev); 83 if (err) 84 return err; 85 86 temp &= ~(MII_DM9161_INTR_STOP); 87 err = phy_write(phydev, MII_DM9161_INTR, temp); 88 } else { 89 temp |= MII_DM9161_INTR_STOP; 90 err = phy_write(phydev, MII_DM9161_INTR, temp); 91 if (err) 92 return err; 93 94 err = dm9161_ack_interrupt(phydev); 95 } 96 97 return err; 98} 99 100static irqreturn_t dm9161_handle_interrupt(struct phy_device *phydev) 101{ 102 int irq_status; 103 104 irq_status = phy_read(phydev, MII_DM9161_INTR); 105 if (irq_status < 0) { 106 phy_error(phydev); 107 return IRQ_NONE; 108 } 109 110 if (!(irq_status & MII_DM9161_INTR_CHANGE)) 111 return IRQ_NONE; 112 113 phy_trigger_machine(phydev); 114 115 return IRQ_HANDLED; 116} 117 118static int dm9161_config_aneg(struct phy_device *phydev) 119{ 120 int err; 121 122 /* Isolate the PHY */ 123 err = phy_write(phydev, MII_BMCR, BMCR_ISOLATE); 124 125 if (err < 0) 126 return err; 127 128 /* Configure the new settings */ 129 err = genphy_config_aneg(phydev); 130 131 if (err < 0) 132 return err; 133 134 return 0; 135} 136 137static int dm9161_config_init(struct phy_device *phydev) 138{ 139 int err, temp; 140 141 /* Isolate the PHY */ 142 err = phy_write(phydev, MII_BMCR, BMCR_ISOLATE); 143 144 if (err < 0) 145 return err; 146 147 switch (phydev->interface) { 148 case PHY_INTERFACE_MODE_MII: 149 temp = MII_DM9161_SCR_INIT; 150 break; 151 case PHY_INTERFACE_MODE_RMII: 152 temp = MII_DM9161_SCR_INIT | MII_DM9161_SCR_RMII; 153 break; 154 default: 155 return -EINVAL; 156 } 157 158 /* Do not bypass the scrambler/descrambler */ 159 err = phy_write(phydev, MII_DM9161_SCR, temp); 160 if (err < 0) 161 return err; 162 163 /* Clear 10BTCSR to default */ 164 err = phy_write(phydev, MII_DM9161_10BTCSR, MII_DM9161_10BTCSR_INIT); 165 166 if (err < 0) 167 return err; 168 169 /* Reconnect the PHY, and enable Autonegotiation */ 170 return phy_write(phydev, MII_BMCR, BMCR_ANENABLE); 171} 172 173static struct phy_driver dm91xx_driver[] = { 174{ 175 .phy_id = 0x0181b880, 176 .name = "Davicom DM9161E", 177 .phy_id_mask = 0x0ffffff0, 178 /* PHY_BASIC_FEATURES */ 179 .config_init = dm9161_config_init, 180 .config_aneg = dm9161_config_aneg, 181 .config_intr = dm9161_config_intr, 182 .handle_interrupt = dm9161_handle_interrupt, 183}, { 184 .phy_id = 0x0181b8b0, 185 .name = "Davicom DM9161B/C", 186 .phy_id_mask = 0x0ffffff0, 187 /* PHY_BASIC_FEATURES */ 188 .config_init = dm9161_config_init, 189 .config_aneg = dm9161_config_aneg, 190 .config_intr = dm9161_config_intr, 191 .handle_interrupt = dm9161_handle_interrupt, 192}, { 193 .phy_id = 0x0181b8a0, 194 .name = "Davicom DM9161A", 195 .phy_id_mask = 0x0ffffff0, 196 /* PHY_BASIC_FEATURES */ 197 .config_init = dm9161_config_init, 198 .config_aneg = dm9161_config_aneg, 199 .config_intr = dm9161_config_intr, 200 .handle_interrupt = dm9161_handle_interrupt, 201}, { 202 .phy_id = 0x00181b80, 203 .name = "Davicom DM9131", 204 .phy_id_mask = 0x0ffffff0, 205 /* PHY_BASIC_FEATURES */ 206 .config_intr = dm9161_config_intr, 207 .handle_interrupt = dm9161_handle_interrupt, 208} }; 209 210module_phy_driver(dm91xx_driver); 211 212static struct mdio_device_id __maybe_unused davicom_tbl[] = { 213 { 0x0181b880, 0x0ffffff0 }, 214 { 0x0181b8b0, 0x0ffffff0 }, 215 { 0x0181b8a0, 0x0ffffff0 }, 216 { 0x00181b80, 0x0ffffff0 }, 217 { } 218}; 219 220MODULE_DEVICE_TABLE(mdio, davicom_tbl);