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 32static struct phy_device *phydev; 33 34static 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 } 63err: 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 69static 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 78static struct notifier_block linkstation_reboot_nb = { 79 .notifier_call = linkstation_reboot_notifier, 80}; 81 82static 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 90static const struct of_device_id ls_poweroff_of_match[] = { 91 { .compatible = "buffalo,ls421d" }, 92 { .compatible = "buffalo,ls421de" }, 93 { }, 94}; 95 96static 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 125static void __exit linkstation_poweroff_exit(void) 126{ 127 pm_power_off = NULL; 128 unregister_reboot_notifier(&linkstation_reboot_nb); 129} 130 131module_init(linkstation_poweroff_init); 132module_exit(linkstation_poweroff_exit); 133 134MODULE_AUTHOR("Daniel González Cabanelas <dgcbueu@gmail.com>"); 135MODULE_DESCRIPTION("LinkStation power off driver"); 136MODULE_LICENSE("GPL v2"); 137