1a2443fd1SAndrew Lunn // SPDX-License-Identifier: GPL-2.0+
249099122SHeiko Schocher /*
349099122SHeiko Schocher * Driver for AMD am79c PHYs
449099122SHeiko Schocher *
549099122SHeiko Schocher * Author: Heiko Schocher <hs@denx.de>
649099122SHeiko Schocher *
749099122SHeiko Schocher * Copyright (c) 2011 DENX Software Engineering GmbH
849099122SHeiko Schocher */
949099122SHeiko Schocher #include <linux/kernel.h>
1049099122SHeiko Schocher #include <linux/errno.h>
1149099122SHeiko Schocher #include <linux/init.h>
1249099122SHeiko Schocher #include <linux/module.h>
1349099122SHeiko Schocher #include <linux/mii.h>
1449099122SHeiko Schocher #include <linux/phy.h>
1549099122SHeiko Schocher
1649099122SHeiko Schocher #define PHY_ID_AM79C874 0x0022561b
1749099122SHeiko Schocher
1849099122SHeiko Schocher #define MII_AM79C_IR 17 /* Interrupt Status/Control Register */
1949099122SHeiko Schocher #define MII_AM79C_IR_EN_LINK 0x0400 /* IR enable Linkstate */
2049099122SHeiko Schocher #define MII_AM79C_IR_EN_ANEG 0x0100 /* IR enable Aneg Complete */
2149099122SHeiko Schocher #define MII_AM79C_IR_IMASK_INIT (MII_AM79C_IR_EN_LINK | MII_AM79C_IR_EN_ANEG)
2249099122SHeiko Schocher
23d995a36bSIoana Ciornei #define MII_AM79C_IR_LINK_DOWN BIT(2)
24d995a36bSIoana Ciornei #define MII_AM79C_IR_ANEG_DONE BIT(0)
25d995a36bSIoana Ciornei #define MII_AM79C_IR_IMASK_STAT (MII_AM79C_IR_LINK_DOWN | MII_AM79C_IR_ANEG_DONE)
26d995a36bSIoana Ciornei
2749099122SHeiko Schocher MODULE_DESCRIPTION("AMD PHY driver");
2849099122SHeiko Schocher MODULE_AUTHOR("Heiko Schocher <hs@denx.de>");
2949099122SHeiko Schocher MODULE_LICENSE("GPL");
3049099122SHeiko Schocher
am79c_ack_interrupt(struct phy_device * phydev)3149099122SHeiko Schocher static int am79c_ack_interrupt(struct phy_device *phydev)
3249099122SHeiko Schocher {
3349099122SHeiko Schocher int err;
3449099122SHeiko Schocher
3549099122SHeiko Schocher err = phy_read(phydev, MII_BMSR);
3649099122SHeiko Schocher if (err < 0)
3749099122SHeiko Schocher return err;
3849099122SHeiko Schocher
3949099122SHeiko Schocher err = phy_read(phydev, MII_AM79C_IR);
4049099122SHeiko Schocher if (err < 0)
4149099122SHeiko Schocher return err;
4249099122SHeiko Schocher
4349099122SHeiko Schocher return 0;
4449099122SHeiko Schocher }
4549099122SHeiko Schocher
am79c_config_init(struct phy_device * phydev)4649099122SHeiko Schocher static int am79c_config_init(struct phy_device *phydev)
4749099122SHeiko Schocher {
4849099122SHeiko Schocher return 0;
4949099122SHeiko Schocher }
5049099122SHeiko Schocher
am79c_config_intr(struct phy_device * phydev)5149099122SHeiko Schocher static int am79c_config_intr(struct phy_device *phydev)
5249099122SHeiko Schocher {
5349099122SHeiko Schocher int err;
5449099122SHeiko Schocher
55*347917c7SIoana Ciornei if (phydev->interrupts == PHY_INTERRUPT_ENABLED) {
56*347917c7SIoana Ciornei err = am79c_ack_interrupt(phydev);
57*347917c7SIoana Ciornei if (err)
58*347917c7SIoana Ciornei return err;
59*347917c7SIoana Ciornei
6049099122SHeiko Schocher err = phy_write(phydev, MII_AM79C_IR, MII_AM79C_IR_IMASK_INIT);
61*347917c7SIoana Ciornei } else {
6249099122SHeiko Schocher err = phy_write(phydev, MII_AM79C_IR, 0);
63*347917c7SIoana Ciornei if (err)
64*347917c7SIoana Ciornei return err;
65*347917c7SIoana Ciornei
66*347917c7SIoana Ciornei err = am79c_ack_interrupt(phydev);
67*347917c7SIoana Ciornei }
6849099122SHeiko Schocher
6949099122SHeiko Schocher return err;
7049099122SHeiko Schocher }
7149099122SHeiko Schocher
am79c_handle_interrupt(struct phy_device * phydev)72d995a36bSIoana Ciornei static irqreturn_t am79c_handle_interrupt(struct phy_device *phydev)
73d995a36bSIoana Ciornei {
74d995a36bSIoana Ciornei int irq_status;
75d995a36bSIoana Ciornei
76d995a36bSIoana Ciornei irq_status = phy_read(phydev, MII_AM79C_IR);
77d995a36bSIoana Ciornei if (irq_status < 0) {
78d995a36bSIoana Ciornei phy_error(phydev);
79d995a36bSIoana Ciornei return IRQ_NONE;
80d995a36bSIoana Ciornei }
81d995a36bSIoana Ciornei
82d995a36bSIoana Ciornei if (!(irq_status & MII_AM79C_IR_IMASK_STAT))
83d995a36bSIoana Ciornei return IRQ_NONE;
84d995a36bSIoana Ciornei
85d995a36bSIoana Ciornei phy_trigger_machine(phydev);
86d995a36bSIoana Ciornei
87d995a36bSIoana Ciornei return IRQ_HANDLED;
88d995a36bSIoana Ciornei }
89d995a36bSIoana Ciornei
90116dffa0SJohan Hovold static struct phy_driver am79c_driver[] = { {
9149099122SHeiko Schocher .phy_id = PHY_ID_AM79C874,
9249099122SHeiko Schocher .name = "AM79C874",
9349099122SHeiko Schocher .phy_id_mask = 0xfffffff0,
94dcdecdcfSHeiner Kallweit /* PHY_BASIC_FEATURES */
9549099122SHeiko Schocher .config_init = am79c_config_init,
9649099122SHeiko Schocher .config_intr = am79c_config_intr,
97d995a36bSIoana Ciornei .handle_interrupt = am79c_handle_interrupt,
98116dffa0SJohan Hovold } };
9949099122SHeiko Schocher
100116dffa0SJohan Hovold module_phy_driver(am79c_driver);
10149099122SHeiko Schocher
10249099122SHeiko Schocher static struct mdio_device_id __maybe_unused amd_tbl[] = {
10349099122SHeiko Schocher { PHY_ID_AM79C874, 0xfffffff0 },
10449099122SHeiko Schocher { }
10549099122SHeiko Schocher };
10649099122SHeiko Schocher
10749099122SHeiko Schocher MODULE_DEVICE_TABLE(mdio, amd_tbl);
108