18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * LinkStation power off restart driver 48c2ecf20Sopenharmony_ci * Copyright (C) 2020 Daniel González Cabanelas <dgcbueu@gmail.com> 58c2ecf20Sopenharmony_ci */ 68c2ecf20Sopenharmony_ci 78c2ecf20Sopenharmony_ci#include <linux/module.h> 88c2ecf20Sopenharmony_ci#include <linux/notifier.h> 98c2ecf20Sopenharmony_ci#include <linux/of.h> 108c2ecf20Sopenharmony_ci#include <linux/of_mdio.h> 118c2ecf20Sopenharmony_ci#include <linux/of_platform.h> 128c2ecf20Sopenharmony_ci#include <linux/reboot.h> 138c2ecf20Sopenharmony_ci#include <linux/phy.h> 148c2ecf20Sopenharmony_ci 158c2ecf20Sopenharmony_ci/* Defines from the eth phy Marvell driver */ 168c2ecf20Sopenharmony_ci#define MII_MARVELL_COPPER_PAGE 0 178c2ecf20Sopenharmony_ci#define MII_MARVELL_LED_PAGE 3 188c2ecf20Sopenharmony_ci#define MII_MARVELL_WOL_PAGE 17 198c2ecf20Sopenharmony_ci#define MII_MARVELL_PHY_PAGE 22 208c2ecf20Sopenharmony_ci 218c2ecf20Sopenharmony_ci#define MII_PHY_LED_CTRL 16 228c2ecf20Sopenharmony_ci#define MII_88E1318S_PHY_LED_TCR 18 238c2ecf20Sopenharmony_ci#define MII_88E1318S_PHY_WOL_CTRL 16 248c2ecf20Sopenharmony_ci#define MII_M1011_IEVENT 19 258c2ecf20Sopenharmony_ci 268c2ecf20Sopenharmony_ci#define MII_88E1318S_PHY_LED_TCR_INTn_ENABLE BIT(7) 278c2ecf20Sopenharmony_ci#define MII_88E1318S_PHY_LED_TCR_FORCE_INT BIT(15) 288c2ecf20Sopenharmony_ci#define MII_88E1318S_PHY_WOL_CTRL_CLEAR_WOL_STATUS BIT(12) 298c2ecf20Sopenharmony_ci#define LED2_FORCE_ON (0x8 << 8) 308c2ecf20Sopenharmony_ci#define LEDMASK GENMASK(11,8) 318c2ecf20Sopenharmony_ci 328c2ecf20Sopenharmony_cistatic struct phy_device *phydev; 338c2ecf20Sopenharmony_ci 348c2ecf20Sopenharmony_cistatic void mvphy_reg_intn(u16 data) 358c2ecf20Sopenharmony_ci{ 368c2ecf20Sopenharmony_ci int rc = 0, saved_page; 378c2ecf20Sopenharmony_ci 388c2ecf20Sopenharmony_ci saved_page = phy_select_page(phydev, MII_MARVELL_LED_PAGE); 398c2ecf20Sopenharmony_ci if (saved_page < 0) 408c2ecf20Sopenharmony_ci goto err; 418c2ecf20Sopenharmony_ci 428c2ecf20Sopenharmony_ci /* Force manual LED2 control to let INTn work */ 438c2ecf20Sopenharmony_ci __phy_modify(phydev, MII_PHY_LED_CTRL, LEDMASK, LED2_FORCE_ON); 448c2ecf20Sopenharmony_ci 458c2ecf20Sopenharmony_ci /* Set the LED[2]/INTn pin to the required state */ 468c2ecf20Sopenharmony_ci __phy_modify(phydev, MII_88E1318S_PHY_LED_TCR, 478c2ecf20Sopenharmony_ci MII_88E1318S_PHY_LED_TCR_FORCE_INT, 488c2ecf20Sopenharmony_ci MII_88E1318S_PHY_LED_TCR_INTn_ENABLE | data); 498c2ecf20Sopenharmony_ci 508c2ecf20Sopenharmony_ci if (!data) { 518c2ecf20Sopenharmony_ci /* Clear interrupts to ensure INTn won't be holded in high state */ 528c2ecf20Sopenharmony_ci __phy_write(phydev, MII_MARVELL_PHY_PAGE, MII_MARVELL_COPPER_PAGE); 538c2ecf20Sopenharmony_ci __phy_read(phydev, MII_M1011_IEVENT); 548c2ecf20Sopenharmony_ci 558c2ecf20Sopenharmony_ci /* If WOL was enabled and a magic packet was received before powering 568c2ecf20Sopenharmony_ci * off, we won't be able to wake up by sending another magic packet. 578c2ecf20Sopenharmony_ci * Clear WOL status. 588c2ecf20Sopenharmony_ci */ 598c2ecf20Sopenharmony_ci __phy_write(phydev, MII_MARVELL_PHY_PAGE, MII_MARVELL_WOL_PAGE); 608c2ecf20Sopenharmony_ci __phy_set_bits(phydev, MII_88E1318S_PHY_WOL_CTRL, 618c2ecf20Sopenharmony_ci MII_88E1318S_PHY_WOL_CTRL_CLEAR_WOL_STATUS); 628c2ecf20Sopenharmony_ci } 638c2ecf20Sopenharmony_cierr: 648c2ecf20Sopenharmony_ci rc = phy_restore_page(phydev, saved_page, rc); 658c2ecf20Sopenharmony_ci if (rc < 0) 668c2ecf20Sopenharmony_ci dev_err(&phydev->mdio.dev, "Write register failed, %d\n", rc); 678c2ecf20Sopenharmony_ci} 688c2ecf20Sopenharmony_ci 698c2ecf20Sopenharmony_cistatic int linkstation_reboot_notifier(struct notifier_block *nb, 708c2ecf20Sopenharmony_ci unsigned long action, void *unused) 718c2ecf20Sopenharmony_ci{ 728c2ecf20Sopenharmony_ci if (action == SYS_RESTART) 738c2ecf20Sopenharmony_ci mvphy_reg_intn(MII_88E1318S_PHY_LED_TCR_FORCE_INT); 748c2ecf20Sopenharmony_ci 758c2ecf20Sopenharmony_ci return NOTIFY_DONE; 768c2ecf20Sopenharmony_ci} 778c2ecf20Sopenharmony_ci 788c2ecf20Sopenharmony_cistatic struct notifier_block linkstation_reboot_nb = { 798c2ecf20Sopenharmony_ci .notifier_call = linkstation_reboot_notifier, 808c2ecf20Sopenharmony_ci}; 818c2ecf20Sopenharmony_ci 828c2ecf20Sopenharmony_cistatic void linkstation_poweroff(void) 838c2ecf20Sopenharmony_ci{ 848c2ecf20Sopenharmony_ci unregister_reboot_notifier(&linkstation_reboot_nb); 858c2ecf20Sopenharmony_ci mvphy_reg_intn(0); 868c2ecf20Sopenharmony_ci 878c2ecf20Sopenharmony_ci kernel_restart("Power off"); 888c2ecf20Sopenharmony_ci} 898c2ecf20Sopenharmony_ci 908c2ecf20Sopenharmony_cistatic const struct of_device_id ls_poweroff_of_match[] = { 918c2ecf20Sopenharmony_ci { .compatible = "buffalo,ls421d" }, 928c2ecf20Sopenharmony_ci { .compatible = "buffalo,ls421de" }, 938c2ecf20Sopenharmony_ci { }, 948c2ecf20Sopenharmony_ci}; 958c2ecf20Sopenharmony_ci 968c2ecf20Sopenharmony_cistatic int __init linkstation_poweroff_init(void) 978c2ecf20Sopenharmony_ci{ 988c2ecf20Sopenharmony_ci struct mii_bus *bus; 998c2ecf20Sopenharmony_ci struct device_node *dn; 1008c2ecf20Sopenharmony_ci 1018c2ecf20Sopenharmony_ci dn = of_find_matching_node(NULL, ls_poweroff_of_match); 1028c2ecf20Sopenharmony_ci if (!dn) 1038c2ecf20Sopenharmony_ci return -ENODEV; 1048c2ecf20Sopenharmony_ci of_node_put(dn); 1058c2ecf20Sopenharmony_ci 1068c2ecf20Sopenharmony_ci dn = of_find_node_by_name(NULL, "mdio"); 1078c2ecf20Sopenharmony_ci if (!dn) 1088c2ecf20Sopenharmony_ci return -ENODEV; 1098c2ecf20Sopenharmony_ci 1108c2ecf20Sopenharmony_ci bus = of_mdio_find_bus(dn); 1118c2ecf20Sopenharmony_ci of_node_put(dn); 1128c2ecf20Sopenharmony_ci if (!bus) 1138c2ecf20Sopenharmony_ci return -EPROBE_DEFER; 1148c2ecf20Sopenharmony_ci 1158c2ecf20Sopenharmony_ci phydev = phy_find_first(bus); 1168c2ecf20Sopenharmony_ci if (!phydev) 1178c2ecf20Sopenharmony_ci return -EPROBE_DEFER; 1188c2ecf20Sopenharmony_ci 1198c2ecf20Sopenharmony_ci register_reboot_notifier(&linkstation_reboot_nb); 1208c2ecf20Sopenharmony_ci pm_power_off = linkstation_poweroff; 1218c2ecf20Sopenharmony_ci 1228c2ecf20Sopenharmony_ci return 0; 1238c2ecf20Sopenharmony_ci} 1248c2ecf20Sopenharmony_ci 1258c2ecf20Sopenharmony_cistatic void __exit linkstation_poweroff_exit(void) 1268c2ecf20Sopenharmony_ci{ 1278c2ecf20Sopenharmony_ci pm_power_off = NULL; 1288c2ecf20Sopenharmony_ci unregister_reboot_notifier(&linkstation_reboot_nb); 1298c2ecf20Sopenharmony_ci} 1308c2ecf20Sopenharmony_ci 1318c2ecf20Sopenharmony_cimodule_init(linkstation_poweroff_init); 1328c2ecf20Sopenharmony_cimodule_exit(linkstation_poweroff_exit); 1338c2ecf20Sopenharmony_ci 1348c2ecf20Sopenharmony_ciMODULE_AUTHOR("Daniel González Cabanelas <dgcbueu@gmail.com>"); 1358c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("LinkStation power off driver"); 1368c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL v2"); 137