^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 1) // SPDX-License-Identifier: GPL-2.0
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 2) /*
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 3) * LinkStation power off restart driver
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 4) * Copyright (C) 2020 Daniel González Cabanelas <dgcbueu@gmail.com>
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 5) */
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 6)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 7) #include <linux/module.h>
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 8) #include <linux/notifier.h>
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 9) #include <linux/of.h>
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 10) #include <linux/of_mdio.h>
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 11) #include <linux/of_platform.h>
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 12) #include <linux/reboot.h>
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 13) #include <linux/phy.h>
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 14)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 15) /* Defines from the eth phy Marvell driver */
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 16) #define MII_MARVELL_COPPER_PAGE 0
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 17) #define MII_MARVELL_LED_PAGE 3
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 18) #define MII_MARVELL_WOL_PAGE 17
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 19) #define MII_MARVELL_PHY_PAGE 22
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 20)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 21) #define MII_PHY_LED_CTRL 16
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 22) #define MII_88E1318S_PHY_LED_TCR 18
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 23) #define MII_88E1318S_PHY_WOL_CTRL 16
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 24) #define MII_M1011_IEVENT 19
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 25)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 26) #define MII_88E1318S_PHY_LED_TCR_INTn_ENABLE BIT(7)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 27) #define MII_88E1318S_PHY_LED_TCR_FORCE_INT BIT(15)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 28) #define MII_88E1318S_PHY_WOL_CTRL_CLEAR_WOL_STATUS BIT(12)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 29) #define LED2_FORCE_ON (0x8 << 8)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 30) #define LEDMASK GENMASK(11,8)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 31)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 32) static struct phy_device *phydev;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 33)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 34) static void mvphy_reg_intn(u16 data)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 35) {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 36) int rc = 0, saved_page;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 37)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 38) saved_page = phy_select_page(phydev, MII_MARVELL_LED_PAGE);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 39) if (saved_page < 0)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 40) goto err;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 41)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 42) /* Force manual LED2 control to let INTn work */
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 43) __phy_modify(phydev, MII_PHY_LED_CTRL, LEDMASK, LED2_FORCE_ON);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 44)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 45) /* Set the LED[2]/INTn pin to the required state */
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 46) __phy_modify(phydev, MII_88E1318S_PHY_LED_TCR,
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 47) MII_88E1318S_PHY_LED_TCR_FORCE_INT,
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 48) MII_88E1318S_PHY_LED_TCR_INTn_ENABLE | data);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 49)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 50) if (!data) {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 51) /* Clear interrupts to ensure INTn won't be holded in high state */
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 52) __phy_write(phydev, MII_MARVELL_PHY_PAGE, MII_MARVELL_COPPER_PAGE);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 53) __phy_read(phydev, MII_M1011_IEVENT);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 54)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 55) /* If WOL was enabled and a magic packet was received before powering
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 56) * off, we won't be able to wake up by sending another magic packet.
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 57) * Clear WOL status.
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 58) */
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 59) __phy_write(phydev, MII_MARVELL_PHY_PAGE, MII_MARVELL_WOL_PAGE);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 60) __phy_set_bits(phydev, MII_88E1318S_PHY_WOL_CTRL,
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 61) MII_88E1318S_PHY_WOL_CTRL_CLEAR_WOL_STATUS);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 62) }
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 63) err:
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 64) rc = phy_restore_page(phydev, saved_page, rc);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 65) if (rc < 0)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 66) dev_err(&phydev->mdio.dev, "Write register failed, %d\n", rc);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 67) }
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 68)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 69) static int linkstation_reboot_notifier(struct notifier_block *nb,
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 70) unsigned long action, void *unused)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 71) {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 72) if (action == SYS_RESTART)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 73) mvphy_reg_intn(MII_88E1318S_PHY_LED_TCR_FORCE_INT);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 74)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 75) return NOTIFY_DONE;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 76) }
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 77)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 78) static struct notifier_block linkstation_reboot_nb = {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 79) .notifier_call = linkstation_reboot_notifier,
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 80) };
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 81)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 82) static void linkstation_poweroff(void)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 83) {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 84) unregister_reboot_notifier(&linkstation_reboot_nb);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 85) mvphy_reg_intn(0);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 86)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 87) kernel_restart("Power off");
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 88) }
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 89)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 90) static const struct of_device_id ls_poweroff_of_match[] = {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 91) { .compatible = "buffalo,ls421d" },
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 92) { .compatible = "buffalo,ls421de" },
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 93) { },
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 94) };
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 95)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 96) static int __init linkstation_poweroff_init(void)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 97) {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 98) struct mii_bus *bus;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 99) struct device_node *dn;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 100)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 101) dn = of_find_matching_node(NULL, ls_poweroff_of_match);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 102) if (!dn)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 103) return -ENODEV;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 104) of_node_put(dn);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 105)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 106) dn = of_find_node_by_name(NULL, "mdio");
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 107) if (!dn)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 108) return -ENODEV;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 109)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 110) bus = of_mdio_find_bus(dn);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 111) of_node_put(dn);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 112) if (!bus)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 113) return -EPROBE_DEFER;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 114)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 115) phydev = phy_find_first(bus);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 116) if (!phydev)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 117) return -EPROBE_DEFER;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 118)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 119) register_reboot_notifier(&linkstation_reboot_nb);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 120) pm_power_off = linkstation_poweroff;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 121)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 122) return 0;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 123) }
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 124)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 125) static void __exit linkstation_poweroff_exit(void)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 126) {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 127) pm_power_off = NULL;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 128) unregister_reboot_notifier(&linkstation_reboot_nb);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 129) }
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 130)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 131) module_init(linkstation_poweroff_init);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 132) module_exit(linkstation_poweroff_exit);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 133)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 134) MODULE_AUTHOR("Daniel González Cabanelas <dgcbueu@gmail.com>");
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 135) MODULE_DESCRIPTION("LinkStation power off driver");
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 136) MODULE_LICENSE("GPL v2");