linux/drivers/power/reset/linkstation-poweroff.c
<<
>>
Prefs
   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