Back to home page

OSCL-LXR

 
 

    


0001 // SPDX-License-Identifier: GPL-2.0
0002 /*
0003  * LinkStation power off restart driver
0004  * Copyright (C) 2020 Daniel González Cabanelas <dgcbueu@gmail.com>
0005  */
0006 
0007 #include <linux/module.h>
0008 #include <linux/notifier.h>
0009 #include <linux/of.h>
0010 #include <linux/of_mdio.h>
0011 #include <linux/of_platform.h>
0012 #include <linux/reboot.h>
0013 #include <linux/phy.h>
0014 
0015 /* Defines from the eth phy Marvell driver */
0016 #define MII_MARVELL_COPPER_PAGE     0
0017 #define MII_MARVELL_LED_PAGE        3
0018 #define MII_MARVELL_WOL_PAGE        17
0019 #define MII_MARVELL_PHY_PAGE        22
0020 
0021 #define MII_PHY_LED_CTRL        16
0022 #define MII_PHY_LED_POL_CTRL        17
0023 #define MII_88E1318S_PHY_LED_TCR    18
0024 #define MII_88E1318S_PHY_WOL_CTRL   16
0025 #define MII_M1011_IEVENT        19
0026 
0027 #define MII_88E1318S_PHY_LED_TCR_INTn_ENABLE        BIT(7)
0028 #define MII_88E1318S_PHY_LED_TCR_FORCE_INT      BIT(15)
0029 #define MII_88E1318S_PHY_WOL_CTRL_CLEAR_WOL_STATUS  BIT(12)
0030 #define LED2_FORCE_ON                   (0x8 << 8)
0031 #define LEDMASK                     GENMASK(11,8)
0032 
0033 #define MII_88E1318S_PHY_LED_POL_LED2       BIT(4)
0034 
0035 struct power_off_cfg {
0036     char *mdio_node_name;
0037     void (*phy_set_reg)(bool restart);
0038 };
0039 
0040 static struct phy_device *phydev;
0041 static const struct power_off_cfg *cfg;
0042 
0043 static void linkstation_mvphy_reg_intn(bool restart)
0044 {
0045     int rc = 0, saved_page;
0046     u16 data = 0;
0047 
0048     if (restart)
0049         data = MII_88E1318S_PHY_LED_TCR_FORCE_INT;
0050 
0051     saved_page = phy_select_page(phydev, MII_MARVELL_LED_PAGE);
0052     if (saved_page < 0)
0053         goto err;
0054 
0055     /* Force manual LED2 control to let INTn work */
0056     __phy_modify(phydev, MII_PHY_LED_CTRL, LEDMASK, LED2_FORCE_ON);
0057 
0058     /* Set the LED[2]/INTn pin to the required state */
0059     __phy_modify(phydev, MII_88E1318S_PHY_LED_TCR,
0060              MII_88E1318S_PHY_LED_TCR_FORCE_INT,
0061              MII_88E1318S_PHY_LED_TCR_INTn_ENABLE | data);
0062 
0063     if (!data) {
0064         /* Clear interrupts to ensure INTn won't be holded in high state */
0065         __phy_write(phydev, MII_MARVELL_PHY_PAGE, MII_MARVELL_COPPER_PAGE);
0066         __phy_read(phydev, MII_M1011_IEVENT);
0067 
0068         /* If WOL was enabled and a magic packet was received before powering
0069          * off, we won't be able to wake up by sending another magic packet.
0070          * Clear WOL status.
0071          */
0072         __phy_write(phydev, MII_MARVELL_PHY_PAGE, MII_MARVELL_WOL_PAGE);
0073         __phy_set_bits(phydev, MII_88E1318S_PHY_WOL_CTRL,
0074                    MII_88E1318S_PHY_WOL_CTRL_CLEAR_WOL_STATUS);
0075     }
0076 err:
0077     rc = phy_restore_page(phydev, saved_page, rc);
0078     if (rc < 0)
0079         dev_err(&phydev->mdio.dev, "Write register failed, %d\n", rc);
0080 }
0081 
0082 static void readynas_mvphy_set_reg(bool restart)
0083 {
0084     int rc = 0, saved_page;
0085     u16 data = 0;
0086 
0087     if (restart)
0088         data = MII_88E1318S_PHY_LED_POL_LED2;
0089 
0090     saved_page = phy_select_page(phydev, MII_MARVELL_LED_PAGE);
0091     if (saved_page < 0)
0092         goto err;
0093 
0094     /* Set the LED[2].0 Polarity bit to the required state */
0095     __phy_modify(phydev, MII_PHY_LED_POL_CTRL,
0096              MII_88E1318S_PHY_LED_POL_LED2, data);
0097 
0098     if (!data) {
0099         /* If WOL was enabled and a magic packet was received before powering
0100          * off, we won't be able to wake up by sending another magic packet.
0101          * Clear WOL status.
0102          */
0103         __phy_write(phydev, MII_MARVELL_PHY_PAGE, MII_MARVELL_WOL_PAGE);
0104         __phy_set_bits(phydev, MII_88E1318S_PHY_WOL_CTRL,
0105                    MII_88E1318S_PHY_WOL_CTRL_CLEAR_WOL_STATUS);
0106     }
0107 err:
0108     rc = phy_restore_page(phydev, saved_page, rc);
0109     if (rc < 0)
0110         dev_err(&phydev->mdio.dev, "Write register failed, %d\n", rc);
0111 }
0112 
0113 static const struct power_off_cfg linkstation_power_off_cfg = {
0114     .mdio_node_name = "mdio",
0115     .phy_set_reg = linkstation_mvphy_reg_intn,
0116 };
0117 
0118 static const struct power_off_cfg readynas_power_off_cfg = {
0119     .mdio_node_name = "mdio-bus",
0120     .phy_set_reg = readynas_mvphy_set_reg,
0121 };
0122 
0123 static int linkstation_reboot_notifier(struct notifier_block *nb,
0124                        unsigned long action, void *unused)
0125 {
0126     if (action == SYS_RESTART)
0127         cfg->phy_set_reg(true);
0128 
0129     return NOTIFY_DONE;
0130 }
0131 
0132 static struct notifier_block linkstation_reboot_nb = {
0133     .notifier_call = linkstation_reboot_notifier,
0134 };
0135 
0136 static void linkstation_poweroff(void)
0137 {
0138     unregister_reboot_notifier(&linkstation_reboot_nb);
0139     cfg->phy_set_reg(false);
0140 
0141     kernel_restart("Power off");
0142 }
0143 
0144 static const struct of_device_id ls_poweroff_of_match[] = {
0145     { .compatible = "buffalo,ls421d",
0146       .data = &linkstation_power_off_cfg,
0147     },
0148     { .compatible = "buffalo,ls421de",
0149       .data = &linkstation_power_off_cfg,
0150     },
0151     { .compatible = "netgear,readynas-duo-v2",
0152       .data = &readynas_power_off_cfg,
0153     },
0154     { },
0155 };
0156 
0157 static int __init linkstation_poweroff_init(void)
0158 {
0159     struct mii_bus *bus;
0160     struct device_node *dn;
0161     const struct of_device_id *match;
0162 
0163     dn = of_find_matching_node(NULL, ls_poweroff_of_match);
0164     if (!dn)
0165         return -ENODEV;
0166     of_node_put(dn);
0167 
0168     match = of_match_node(ls_poweroff_of_match, dn);
0169     cfg = match->data;
0170 
0171     dn = of_find_node_by_name(NULL, cfg->mdio_node_name);
0172     if (!dn)
0173         return -ENODEV;
0174 
0175     bus = of_mdio_find_bus(dn);
0176     of_node_put(dn);
0177     if (!bus)
0178         return -EPROBE_DEFER;
0179 
0180     phydev = phy_find_first(bus);
0181     put_device(&bus->dev);
0182     if (!phydev)
0183         return -EPROBE_DEFER;
0184 
0185     register_reboot_notifier(&linkstation_reboot_nb);
0186     pm_power_off = linkstation_poweroff;
0187 
0188     return 0;
0189 }
0190 
0191 static void __exit linkstation_poweroff_exit(void)
0192 {
0193     pm_power_off = NULL;
0194     unregister_reboot_notifier(&linkstation_reboot_nb);
0195 }
0196 
0197 module_init(linkstation_poweroff_init);
0198 module_exit(linkstation_poweroff_exit);
0199 
0200 MODULE_AUTHOR("Daniel González Cabanelas <dgcbueu@gmail.com>");
0201 MODULE_DESCRIPTION("LinkStation power off driver");
0202 MODULE_LICENSE("GPL v2");