1 // SPDX-License-Identifier: GPL-2.0
3 * LinkStation power off restart driver
4 * Copyright (C) 2020 Daniel González Cabanelas <dgcbueu@gmail.com>
7 #include <linux/module.h>
8 #include <linux/notifier.h>
10 #include <linux/of_mdio.h>
11 #include <linux/of_platform.h>
12 #include <linux/reboot.h>
13 #include <linux/phy.h>
15 /* Defines from the eth phy Marvell driver */
16 #define MII_MARVELL_COPPER_PAGE 0
17 #define MII_MARVELL_LED_PAGE 3
18 #define MII_MARVELL_WOL_PAGE 17
19 #define MII_MARVELL_PHY_PAGE 22
21 #define MII_PHY_LED_CTRL 16
22 #define MII_88E1318S_PHY_LED_TCR 18
23 #define MII_88E1318S_PHY_WOL_CTRL 16
24 #define MII_M1011_IEVENT 19
26 #define MII_88E1318S_PHY_LED_TCR_INTn_ENABLE BIT(7)
27 #define MII_88E1318S_PHY_LED_TCR_FORCE_INT BIT(15)
28 #define MII_88E1318S_PHY_WOL_CTRL_CLEAR_WOL_STATUS BIT(12)
29 #define LED2_FORCE_ON (0x8 << 8)
30 #define LEDMASK GENMASK(11,8)
32 static struct phy_device
*phydev
;
34 static void mvphy_reg_intn(u16 data
)
36 int rc
= 0, saved_page
;
38 saved_page
= phy_select_page(phydev
, MII_MARVELL_LED_PAGE
);
42 /* Force manual LED2 control to let INTn work */
43 __phy_modify(phydev
, MII_PHY_LED_CTRL
, LEDMASK
, LED2_FORCE_ON
);
45 /* Set the LED[2]/INTn pin to the required state */
46 __phy_modify(phydev
, MII_88E1318S_PHY_LED_TCR
,
47 MII_88E1318S_PHY_LED_TCR_FORCE_INT
,
48 MII_88E1318S_PHY_LED_TCR_INTn_ENABLE
| data
);
51 /* Clear interrupts to ensure INTn won't be holded in high state */
52 __phy_write(phydev
, MII_MARVELL_PHY_PAGE
, MII_MARVELL_COPPER_PAGE
);
53 __phy_read(phydev
, MII_M1011_IEVENT
);
55 /* If WOL was enabled and a magic packet was received before powering
56 * off, we won't be able to wake up by sending another magic packet.
59 __phy_write(phydev
, MII_MARVELL_PHY_PAGE
, MII_MARVELL_WOL_PAGE
);
60 __phy_set_bits(phydev
, MII_88E1318S_PHY_WOL_CTRL
,
61 MII_88E1318S_PHY_WOL_CTRL_CLEAR_WOL_STATUS
);
64 rc
= phy_restore_page(phydev
, saved_page
, rc
);
66 dev_err(&phydev
->mdio
.dev
, "Write register failed, %d\n", rc
);
69 static int linkstation_reboot_notifier(struct notifier_block
*nb
,
70 unsigned long action
, void *unused
)
72 if (action
== SYS_RESTART
)
73 mvphy_reg_intn(MII_88E1318S_PHY_LED_TCR_FORCE_INT
);
78 static struct notifier_block linkstation_reboot_nb
= {
79 .notifier_call
= linkstation_reboot_notifier
,
82 static void linkstation_poweroff(void)
84 unregister_reboot_notifier(&linkstation_reboot_nb
);
87 kernel_restart("Power off");
90 static const struct of_device_id ls_poweroff_of_match
[] = {
91 { .compatible
= "buffalo,ls421d" },
92 { .compatible
= "buffalo,ls421de" },
96 static int __init
linkstation_poweroff_init(void)
99 struct device_node
*dn
;
101 dn
= of_find_matching_node(NULL
, ls_poweroff_of_match
);
106 dn
= of_find_node_by_name(NULL
, "mdio");
110 bus
= of_mdio_find_bus(dn
);
113 return -EPROBE_DEFER
;
115 phydev
= phy_find_first(bus
);
117 return -EPROBE_DEFER
;
119 register_reboot_notifier(&linkstation_reboot_nb
);
120 pm_power_off
= linkstation_poweroff
;
125 static void __exit
linkstation_poweroff_exit(void)
128 unregister_reboot_notifier(&linkstation_reboot_nb
);
131 module_init(linkstation_poweroff_init
);
132 module_exit(linkstation_poweroff_exit
);
134 MODULE_AUTHOR("Daniel González Cabanelas <dgcbueu@gmail.com>");
135 MODULE_DESCRIPTION("LinkStation power off driver");
136 MODULE_LICENSE("GPL v2");