221 lines · c
1// SPDX-License-Identifier: GPL-2.0+2/*3 * drivers/net/phy/davicom.c4 *5 * Driver for Davicom PHYs6 *7 * Author: Andy Fleming8 *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 0x1033#define MII_DM9161_SCR_INIT 0x061034#define MII_DM9161_SCR_RMII 0x010035 36/* DM9161 Interrupt Register */37#define MII_DM9161_INTR 0x1538#define MII_DM9161_INTR_PEND 0x800039#define MII_DM9161_INTR_DPLX_MASK 0x080040#define MII_DM9161_INTR_SPD_MASK 0x040041#define MII_DM9161_INTR_LINK_MASK 0x020042#define MII_DM9161_INTR_MASK 0x010043#define MII_DM9161_INTR_DPLX_CHANGE 0x001044#define MII_DM9161_INTR_SPD_CHANGE 0x000845#define MII_DM9161_INTR_LINK_CHANGE 0x000446#define MII_DM9161_INTR_INIT 0x000047#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 0x1257#define MII_DM9161_10BTCSR_INIT 0x780058 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 172static 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);221