1 // SPDX-License-Identifier: GPL-2.0 2 /* 3 * LinkStation power off restart driver 4 * Copyright (C) 2020 Daniel González Cabanelas <dgcbueu@gmail.com> 5 */ 6 7 #include <linux/module.h> 8 #include <linux/notifier.h> 9 #include <linux/of.h> 10 #include <linux/of_mdio.h> 11 #include <linux/of_platform.h> 12 #include <linux/reboot.h> 13 #include <linux/phy.h> 14 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 20 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 25 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) 31 32 static struct phy_device *phydev; 33 34 static void mvphy_reg_intn(u16 data) 35 { 36 int rc = 0, saved_page; 37 38 saved_page = phy_select_page(phydev, MII_MARVELL_LED_PAGE); 39 if (saved_page < 0) 40 goto err; 41 42 /* Force manual LED2 control to let INTn work */ 43 __phy_modify(phydev, MII_PHY_LED_CTRL, LEDMASK, LED2_FORCE_ON); 44 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); 49 50 if (!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); 54 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. 57 * Clear WOL status. 58 */ 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); 62 } 63 err: 64 rc = phy_restore_page(phydev, saved_page, rc); 65 if (rc < 0) 66 dev_err(&phydev->mdio.dev, "Write register failed, %d\n", rc); 67 } 68 69 static int linkstation_reboot_notifier(struct notifier_block *nb, 70 unsigned long action, void *unused) 71 { 72 if (action == SYS_RESTART) 73 mvphy_reg_intn(MII_88E1318S_PHY_LED_TCR_FORCE_INT); 74 75 return NOTIFY_DONE; 76 } 77 78 static struct notifier_block linkstation_reboot_nb = { 79 .notifier_call = linkstation_reboot_notifier, 80 }; 81 82 static void linkstation_poweroff(void) 83 { 84 unregister_reboot_notifier(&linkstation_reboot_nb); 85 mvphy_reg_intn(0); 86 87 kernel_restart("Power off"); 88 } 89 90 static const struct of_device_id ls_poweroff_of_match[] = { 91 { .compatible = "buffalo,ls421d" }, 92 { .compatible = "buffalo,ls421de" }, 93 { }, 94 }; 95 96 static int __init linkstation_poweroff_init(void) 97 { 98 struct mii_bus *bus; 99 struct device_node *dn; 100 101 dn = of_find_matching_node(NULL, ls_poweroff_of_match); 102 if (!dn) 103 return -ENODEV; 104 of_node_put(dn); 105 106 dn = of_find_node_by_name(NULL, "mdio"); 107 if (!dn) 108 return -ENODEV; 109 110 bus = of_mdio_find_bus(dn); 111 of_node_put(dn); 112 if (!bus) 113 return -EPROBE_DEFER; 114 115 phydev = phy_find_first(bus); 116 if (!phydev) 117 return -EPROBE_DEFER; 118 119 register_reboot_notifier(&linkstation_reboot_nb); 120 pm_power_off = linkstation_poweroff; 121 122 return 0; 123 } 124 125 static void __exit linkstation_poweroff_exit(void) 126 { 127 pm_power_off = NULL; 128 unregister_reboot_notifier(&linkstation_reboot_nb); 129 } 130 131 module_init(linkstation_poweroff_init); 132 module_exit(linkstation_poweroff_exit); 133 134 MODULE_AUTHOR("Daniel González Cabanelas <dgcbueu@gmail.com>"); 135 MODULE_DESCRIPTION("LinkStation power off driver"); 136 MODULE_LICENSE("GPL v2"); 137