@@ -16,6 +16,7 @@
#include <linux/property.h>
#include <linux/wordpart.h>
#include <linux/unaligned.h>
+#include <linux/clk-provider.h>
#define EN8811H_PHY_ID 0x03a2a411
@@ -112,6 +113,11 @@
#define EN8811H_POLARITY_TX_NORMAL BIT(0)
#define EN8811H_POLARITY_RX_REVERSE BIT(1)
+#define EN8811H_CLK_CGM 0xcf958
+#define EN8811H_CLK_CGM_CKO BIT(26)
+#define EN8811H_HWTRAP1 0xcf914
+#define EN8811H_HWTRAP1_CKO BIT(12)
+
#define EN8811H_GPIO_OUTPUT 0xcf8b8
#define EN8811H_GPIO_OUTPUT_345 (BIT(3) | BIT(4) | BIT(5))
@@ -142,10 +148,15 @@ struct led {
unsigned long state;
};
+#define to_en8811h_priv(_hw) \
+ container_of(_hw, struct en8811h_priv, hw)
+
struct en8811h_priv {
u32 firmware_version;
bool mcu_needs_restart;
struct led led[EN8811H_LED_COUNT];
+ struct clk_hw hw;
+ struct phy_device *phydev;
};
enum {
@@ -806,6 +817,84 @@ static int en8811h_led_hw_is_supported(struct phy_device *phydev, u8 index,
return 0;
};
+static unsigned long en8811h_recalc_rate(struct clk_hw *hw, unsigned long parent)
+{
+ struct en8811h_priv *priv = to_en8811h_priv(hw);
+ struct phy_device *phydev = priv->phydev;
+ u32 pbus_value;
+ int ret;
+
+ ret = air_buckpbus_reg_read(phydev, EN8811H_HWTRAP1, &pbus_value);
+ if (ret < 0)
+ return ret;
+
+ return (pbus_value & EN8811H_HWTRAP1_CKO) ? 50000000 : 25000000;
+}
+
+static int en8811h_enable(struct clk_hw *hw)
+{
+ struct en8811h_priv *priv = to_en8811h_priv(hw);
+ struct phy_device *phydev = priv->phydev;
+
+ return air_buckpbus_reg_modify(phydev, EN8811H_CLK_CGM,
+ EN8811H_CLK_CGM_CKO, EN8811H_CLK_CGM_CKO);
+}
+
+static void en8811h_disable(struct clk_hw *hw)
+{
+ struct en8811h_priv *priv = to_en8811h_priv(hw);
+ struct phy_device *phydev = priv->phydev;
+
+ air_buckpbus_reg_modify(phydev, EN8811H_CLK_CGM,
+ EN8811H_CLK_CGM_CKO, 0);
+}
+
+static int en8811h_is_enabled(struct clk_hw *hw)
+{
+ struct en8811h_priv *priv = to_en8811h_priv(hw);
+ struct phy_device *phydev = priv->phydev;
+ int ret = 0;
+ u32 pbus_value;
+
+ ret = air_buckpbus_reg_read(phydev, EN8811H_CLK_CGM, &pbus_value);
+ if (ret < 0)
+ return ret;
+
+ return (pbus_value & EN8811H_CLK_CGM_CKO);
+}
+
+static const struct clk_ops en8811h_clk_ops = {
+ .recalc_rate = en8811h_recalc_rate,
+ .enable = en8811h_enable,
+ .disable = en8811h_disable,
+ .is_enabled = en8811h_is_enabled,
+};
+
+static int en8811h_clk_provider_setup(struct device *dev, struct clk_hw *hw)
+{
+ struct clk_init_data init;
+ int ret;
+
+ if (!IS_ENABLED(CONFIG_COMMON_CLK))
+ return 0;
+
+ init.name = devm_kasprintf(dev, GFP_KERNEL, "%s-clk",
+ fwnode_get_name(dev_fwnode(dev)));
+ if (!init.name)
+ return -ENOMEM;
+
+ init.ops = &en8811h_clk_ops;
+ init.flags = CLK_GET_RATE_NOCACHE;
+ init.num_parents = 0;
+ hw->init = &init;
+
+ ret = devm_clk_hw_register(dev, hw);
+ if (ret)
+ return ret;
+
+ return devm_of_clk_add_hw_provider(dev, of_clk_hw_simple_get, hw);
+}
+
static int en8811h_probe(struct phy_device *phydev)
{
struct en8811h_priv *priv;
@@ -838,6 +927,12 @@ static int en8811h_probe(struct phy_device *phydev)
return ret;
}
+ priv->phydev = phydev;
+ /* Co-Clock */
+ ret = en8811h_clk_provider_setup(&phydev->mdio.dev, &priv->hw);
+ if (ret)
+ return ret;
+
/* Configure led gpio pins as output */
ret = air_buckpbus_reg_modify(phydev, EN8811H_GPIO_OUTPUT,
EN8811H_GPIO_OUTPUT_345,
The EN8811H generates 25MHz or 50MHz clocks on its CKO pin, selected by GPIO3 hardware trap. Register 0xcf914, read via buckpbus API, shows the frequency with bit 12: 0 for 25MHz, 1 for 50MHz. CKO clock output is active from power-up through md32 firmware loading. Signed-off-by: Lucien.Jheng <lucienX123@gmail.com> --- drivers/net/phy/air_en8811h.c | 95 +++++++++++++++++++++++++++++++++++ 1 file changed, 95 insertions(+)