cachepc-linux

Fork of AMDESE/linux with modifications for CachePC side-channel attack
git clone https://git.sinitax.com/sinitax/cachepc-linux
Log | Files | Refs | README | LICENSE | sfeed.txt

linkstation-poweroff.c (5328B)


      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_PHY_LED_POL_CTRL		17
     23#define MII_88E1318S_PHY_LED_TCR	18
     24#define MII_88E1318S_PHY_WOL_CTRL	16
     25#define MII_M1011_IEVENT		19
     26
     27#define MII_88E1318S_PHY_LED_TCR_INTn_ENABLE		BIT(7)
     28#define MII_88E1318S_PHY_LED_TCR_FORCE_INT		BIT(15)
     29#define MII_88E1318S_PHY_WOL_CTRL_CLEAR_WOL_STATUS	BIT(12)
     30#define LED2_FORCE_ON					(0x8 << 8)
     31#define LEDMASK						GENMASK(11,8)
     32
     33#define MII_88E1318S_PHY_LED_POL_LED2		BIT(4)
     34
     35struct power_off_cfg {
     36	char *mdio_node_name;
     37	void (*phy_set_reg)(bool restart);
     38};
     39
     40static struct phy_device *phydev;
     41static const struct power_off_cfg *cfg;
     42
     43static void linkstation_mvphy_reg_intn(bool restart)
     44{
     45	int rc = 0, saved_page;
     46	u16 data = 0;
     47
     48	if (restart)
     49		data = MII_88E1318S_PHY_LED_TCR_FORCE_INT;
     50
     51	saved_page = phy_select_page(phydev, MII_MARVELL_LED_PAGE);
     52	if (saved_page < 0)
     53		goto err;
     54
     55	/* Force manual LED2 control to let INTn work */
     56	__phy_modify(phydev, MII_PHY_LED_CTRL, LEDMASK, LED2_FORCE_ON);
     57
     58	/* Set the LED[2]/INTn pin to the required state */
     59	__phy_modify(phydev, MII_88E1318S_PHY_LED_TCR,
     60		     MII_88E1318S_PHY_LED_TCR_FORCE_INT,
     61		     MII_88E1318S_PHY_LED_TCR_INTn_ENABLE | data);
     62
     63	if (!data) {
     64		/* Clear interrupts to ensure INTn won't be holded in high state */
     65		__phy_write(phydev, MII_MARVELL_PHY_PAGE, MII_MARVELL_COPPER_PAGE);
     66		__phy_read(phydev, MII_M1011_IEVENT);
     67
     68		/* If WOL was enabled and a magic packet was received before powering
     69		 * off, we won't be able to wake up by sending another magic packet.
     70		 * Clear WOL status.
     71		 */
     72		__phy_write(phydev, MII_MARVELL_PHY_PAGE, MII_MARVELL_WOL_PAGE);
     73		__phy_set_bits(phydev, MII_88E1318S_PHY_WOL_CTRL,
     74			       MII_88E1318S_PHY_WOL_CTRL_CLEAR_WOL_STATUS);
     75	}
     76err:
     77	rc = phy_restore_page(phydev, saved_page, rc);
     78	if (rc < 0)
     79		dev_err(&phydev->mdio.dev, "Write register failed, %d\n", rc);
     80}
     81
     82static void readynas_mvphy_set_reg(bool restart)
     83{
     84	int rc = 0, saved_page;
     85	u16 data = 0;
     86
     87	if (restart)
     88		data = MII_88E1318S_PHY_LED_POL_LED2;
     89
     90	saved_page = phy_select_page(phydev, MII_MARVELL_LED_PAGE);
     91	if (saved_page < 0)
     92		goto err;
     93
     94	/* Set the LED[2].0 Polarity bit to the required state */
     95	__phy_modify(phydev, MII_PHY_LED_POL_CTRL,
     96		     MII_88E1318S_PHY_LED_POL_LED2, data);
     97
     98	if (!data) {
     99		/* If WOL was enabled and a magic packet was received before powering
    100		 * off, we won't be able to wake up by sending another magic packet.
    101		 * Clear WOL status.
    102		 */
    103		__phy_write(phydev, MII_MARVELL_PHY_PAGE, MII_MARVELL_WOL_PAGE);
    104		__phy_set_bits(phydev, MII_88E1318S_PHY_WOL_CTRL,
    105			       MII_88E1318S_PHY_WOL_CTRL_CLEAR_WOL_STATUS);
    106	}
    107err:
    108	rc = phy_restore_page(phydev, saved_page, rc);
    109	if (rc < 0)
    110		dev_err(&phydev->mdio.dev, "Write register failed, %d\n", rc);
    111}
    112
    113static const struct power_off_cfg linkstation_power_off_cfg = {
    114	.mdio_node_name = "mdio",
    115	.phy_set_reg = linkstation_mvphy_reg_intn,
    116};
    117
    118static const struct power_off_cfg readynas_power_off_cfg = {
    119	.mdio_node_name = "mdio-bus",
    120	.phy_set_reg = readynas_mvphy_set_reg,
    121};
    122
    123static int linkstation_reboot_notifier(struct notifier_block *nb,
    124				       unsigned long action, void *unused)
    125{
    126	if (action == SYS_RESTART)
    127		cfg->phy_set_reg(true);
    128
    129	return NOTIFY_DONE;
    130}
    131
    132static struct notifier_block linkstation_reboot_nb = {
    133	.notifier_call = linkstation_reboot_notifier,
    134};
    135
    136static void linkstation_poweroff(void)
    137{
    138	unregister_reboot_notifier(&linkstation_reboot_nb);
    139	cfg->phy_set_reg(false);
    140
    141	kernel_restart("Power off");
    142}
    143
    144static const struct of_device_id ls_poweroff_of_match[] = {
    145	{ .compatible = "buffalo,ls421d",
    146	  .data = &linkstation_power_off_cfg,
    147	},
    148	{ .compatible = "buffalo,ls421de",
    149	  .data = &linkstation_power_off_cfg,
    150	},
    151	{ .compatible = "netgear,readynas-duo-v2",
    152	  .data = &readynas_power_off_cfg,
    153	},
    154	{ },
    155};
    156
    157static int __init linkstation_poweroff_init(void)
    158{
    159	struct mii_bus *bus;
    160	struct device_node *dn;
    161	const struct of_device_id *match;
    162
    163	dn = of_find_matching_node(NULL, ls_poweroff_of_match);
    164	if (!dn)
    165		return -ENODEV;
    166	of_node_put(dn);
    167
    168	match = of_match_node(ls_poweroff_of_match, dn);
    169	cfg = match->data;
    170
    171	dn = of_find_node_by_name(NULL, cfg->mdio_node_name);
    172	if (!dn)
    173		return -ENODEV;
    174
    175	bus = of_mdio_find_bus(dn);
    176	of_node_put(dn);
    177	if (!bus)
    178		return -EPROBE_DEFER;
    179
    180	phydev = phy_find_first(bus);
    181	put_device(&bus->dev);
    182	if (!phydev)
    183		return -EPROBE_DEFER;
    184
    185	register_reboot_notifier(&linkstation_reboot_nb);
    186	pm_power_off = linkstation_poweroff;
    187
    188	return 0;
    189}
    190
    191static void __exit linkstation_poweroff_exit(void)
    192{
    193	pm_power_off = NULL;
    194	unregister_reboot_notifier(&linkstation_reboot_nb);
    195}
    196
    197module_init(linkstation_poweroff_init);
    198module_exit(linkstation_poweroff_exit);
    199
    200MODULE_AUTHOR("Daniel González Cabanelas <dgcbueu@gmail.com>");
    201MODULE_DESCRIPTION("LinkStation power off driver");
    202MODULE_LICENSE("GPL v2");