162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Simple memory-mapped device MDIO MUX driver 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Author: Timur Tabi <timur@freescale.com> 662306a36Sopenharmony_ci * 762306a36Sopenharmony_ci * Copyright 2012 Freescale Semiconductor, Inc. 862306a36Sopenharmony_ci */ 962306a36Sopenharmony_ci 1062306a36Sopenharmony_ci#include <linux/device.h> 1162306a36Sopenharmony_ci#include <linux/mdio-mux.h> 1262306a36Sopenharmony_ci#include <linux/module.h> 1362306a36Sopenharmony_ci#include <linux/of_address.h> 1462306a36Sopenharmony_ci#include <linux/of_mdio.h> 1562306a36Sopenharmony_ci#include <linux/phy.h> 1662306a36Sopenharmony_ci#include <linux/platform_device.h> 1762306a36Sopenharmony_ci 1862306a36Sopenharmony_cistruct mdio_mux_mmioreg_state { 1962306a36Sopenharmony_ci void *mux_handle; 2062306a36Sopenharmony_ci phys_addr_t phys; 2162306a36Sopenharmony_ci unsigned int iosize; 2262306a36Sopenharmony_ci unsigned int mask; 2362306a36Sopenharmony_ci}; 2462306a36Sopenharmony_ci 2562306a36Sopenharmony_ci/* 2662306a36Sopenharmony_ci * MDIO multiplexing switch function 2762306a36Sopenharmony_ci * 2862306a36Sopenharmony_ci * This function is called by the mdio-mux layer when it thinks the mdio bus 2962306a36Sopenharmony_ci * multiplexer needs to switch. 3062306a36Sopenharmony_ci * 3162306a36Sopenharmony_ci * 'current_child' is the current value of the mux register (masked via 3262306a36Sopenharmony_ci * s->mask). 3362306a36Sopenharmony_ci * 3462306a36Sopenharmony_ci * 'desired_child' is the value of the 'reg' property of the target child MDIO 3562306a36Sopenharmony_ci * node. 3662306a36Sopenharmony_ci * 3762306a36Sopenharmony_ci * The first time this function is called, current_child == -1. 3862306a36Sopenharmony_ci * 3962306a36Sopenharmony_ci * If current_child == desired_child, then the mux is already set to the 4062306a36Sopenharmony_ci * correct bus. 4162306a36Sopenharmony_ci */ 4262306a36Sopenharmony_cistatic int mdio_mux_mmioreg_switch_fn(int current_child, int desired_child, 4362306a36Sopenharmony_ci void *data) 4462306a36Sopenharmony_ci{ 4562306a36Sopenharmony_ci struct mdio_mux_mmioreg_state *s = data; 4662306a36Sopenharmony_ci 4762306a36Sopenharmony_ci if (current_child ^ desired_child) { 4862306a36Sopenharmony_ci void __iomem *p = ioremap(s->phys, s->iosize); 4962306a36Sopenharmony_ci if (!p) 5062306a36Sopenharmony_ci return -ENOMEM; 5162306a36Sopenharmony_ci 5262306a36Sopenharmony_ci switch (s->iosize) { 5362306a36Sopenharmony_ci case sizeof(uint8_t): { 5462306a36Sopenharmony_ci uint8_t x, y; 5562306a36Sopenharmony_ci 5662306a36Sopenharmony_ci x = ioread8(p); 5762306a36Sopenharmony_ci y = (x & ~s->mask) | desired_child; 5862306a36Sopenharmony_ci if (x != y) { 5962306a36Sopenharmony_ci iowrite8((x & ~s->mask) | desired_child, p); 6062306a36Sopenharmony_ci pr_debug("%s: %02x -> %02x\n", __func__, x, y); 6162306a36Sopenharmony_ci } 6262306a36Sopenharmony_ci 6362306a36Sopenharmony_ci break; 6462306a36Sopenharmony_ci } 6562306a36Sopenharmony_ci case sizeof(uint16_t): { 6662306a36Sopenharmony_ci uint16_t x, y; 6762306a36Sopenharmony_ci 6862306a36Sopenharmony_ci x = ioread16(p); 6962306a36Sopenharmony_ci y = (x & ~s->mask) | desired_child; 7062306a36Sopenharmony_ci if (x != y) { 7162306a36Sopenharmony_ci iowrite16((x & ~s->mask) | desired_child, p); 7262306a36Sopenharmony_ci pr_debug("%s: %04x -> %04x\n", __func__, x, y); 7362306a36Sopenharmony_ci } 7462306a36Sopenharmony_ci 7562306a36Sopenharmony_ci break; 7662306a36Sopenharmony_ci } 7762306a36Sopenharmony_ci case sizeof(uint32_t): { 7862306a36Sopenharmony_ci uint32_t x, y; 7962306a36Sopenharmony_ci 8062306a36Sopenharmony_ci x = ioread32(p); 8162306a36Sopenharmony_ci y = (x & ~s->mask) | desired_child; 8262306a36Sopenharmony_ci if (x != y) { 8362306a36Sopenharmony_ci iowrite32((x & ~s->mask) | desired_child, p); 8462306a36Sopenharmony_ci pr_debug("%s: %08x -> %08x\n", __func__, x, y); 8562306a36Sopenharmony_ci } 8662306a36Sopenharmony_ci 8762306a36Sopenharmony_ci break; 8862306a36Sopenharmony_ci } 8962306a36Sopenharmony_ci } 9062306a36Sopenharmony_ci 9162306a36Sopenharmony_ci iounmap(p); 9262306a36Sopenharmony_ci } 9362306a36Sopenharmony_ci 9462306a36Sopenharmony_ci return 0; 9562306a36Sopenharmony_ci} 9662306a36Sopenharmony_ci 9762306a36Sopenharmony_cistatic int mdio_mux_mmioreg_probe(struct platform_device *pdev) 9862306a36Sopenharmony_ci{ 9962306a36Sopenharmony_ci struct device_node *np2, *np = pdev->dev.of_node; 10062306a36Sopenharmony_ci struct mdio_mux_mmioreg_state *s; 10162306a36Sopenharmony_ci struct resource res; 10262306a36Sopenharmony_ci const __be32 *iprop; 10362306a36Sopenharmony_ci int len, ret; 10462306a36Sopenharmony_ci 10562306a36Sopenharmony_ci dev_dbg(&pdev->dev, "probing node %pOF\n", np); 10662306a36Sopenharmony_ci 10762306a36Sopenharmony_ci s = devm_kzalloc(&pdev->dev, sizeof(*s), GFP_KERNEL); 10862306a36Sopenharmony_ci if (!s) 10962306a36Sopenharmony_ci return -ENOMEM; 11062306a36Sopenharmony_ci 11162306a36Sopenharmony_ci ret = of_address_to_resource(np, 0, &res); 11262306a36Sopenharmony_ci if (ret) { 11362306a36Sopenharmony_ci dev_err(&pdev->dev, "could not obtain memory map for node %pOF\n", 11462306a36Sopenharmony_ci np); 11562306a36Sopenharmony_ci return ret; 11662306a36Sopenharmony_ci } 11762306a36Sopenharmony_ci s->phys = res.start; 11862306a36Sopenharmony_ci 11962306a36Sopenharmony_ci s->iosize = resource_size(&res); 12062306a36Sopenharmony_ci if (s->iosize != sizeof(uint8_t) && 12162306a36Sopenharmony_ci s->iosize != sizeof(uint16_t) && 12262306a36Sopenharmony_ci s->iosize != sizeof(uint32_t)) { 12362306a36Sopenharmony_ci dev_err(&pdev->dev, "only 8/16/32-bit registers are supported\n"); 12462306a36Sopenharmony_ci return -EINVAL; 12562306a36Sopenharmony_ci } 12662306a36Sopenharmony_ci 12762306a36Sopenharmony_ci iprop = of_get_property(np, "mux-mask", &len); 12862306a36Sopenharmony_ci if (!iprop || len != sizeof(uint32_t)) { 12962306a36Sopenharmony_ci dev_err(&pdev->dev, "missing or invalid mux-mask property\n"); 13062306a36Sopenharmony_ci return -ENODEV; 13162306a36Sopenharmony_ci } 13262306a36Sopenharmony_ci if (be32_to_cpup(iprop) >= BIT(s->iosize * 8)) { 13362306a36Sopenharmony_ci dev_err(&pdev->dev, "only 8/16/32-bit registers are supported\n"); 13462306a36Sopenharmony_ci return -EINVAL; 13562306a36Sopenharmony_ci } 13662306a36Sopenharmony_ci s->mask = be32_to_cpup(iprop); 13762306a36Sopenharmony_ci 13862306a36Sopenharmony_ci /* 13962306a36Sopenharmony_ci * Verify that the 'reg' property of each child MDIO bus does not 14062306a36Sopenharmony_ci * set any bits outside of the 'mask'. 14162306a36Sopenharmony_ci */ 14262306a36Sopenharmony_ci for_each_available_child_of_node(np, np2) { 14362306a36Sopenharmony_ci u64 reg; 14462306a36Sopenharmony_ci 14562306a36Sopenharmony_ci if (of_property_read_reg(np2, 0, ®, NULL)) { 14662306a36Sopenharmony_ci dev_err(&pdev->dev, "mdio-mux child node %pOF is " 14762306a36Sopenharmony_ci "missing a 'reg' property\n", np2); 14862306a36Sopenharmony_ci of_node_put(np2); 14962306a36Sopenharmony_ci return -ENODEV; 15062306a36Sopenharmony_ci } 15162306a36Sopenharmony_ci if ((u32)reg & ~s->mask) { 15262306a36Sopenharmony_ci dev_err(&pdev->dev, "mdio-mux child node %pOF has " 15362306a36Sopenharmony_ci "a 'reg' value with unmasked bits\n", 15462306a36Sopenharmony_ci np2); 15562306a36Sopenharmony_ci of_node_put(np2); 15662306a36Sopenharmony_ci return -ENODEV; 15762306a36Sopenharmony_ci } 15862306a36Sopenharmony_ci } 15962306a36Sopenharmony_ci 16062306a36Sopenharmony_ci ret = mdio_mux_init(&pdev->dev, pdev->dev.of_node, 16162306a36Sopenharmony_ci mdio_mux_mmioreg_switch_fn, 16262306a36Sopenharmony_ci &s->mux_handle, s, NULL); 16362306a36Sopenharmony_ci if (ret) 16462306a36Sopenharmony_ci return dev_err_probe(&pdev->dev, ret, 16562306a36Sopenharmony_ci "failed to register mdio-mux bus %pOF\n", np); 16662306a36Sopenharmony_ci 16762306a36Sopenharmony_ci pdev->dev.platform_data = s; 16862306a36Sopenharmony_ci 16962306a36Sopenharmony_ci return 0; 17062306a36Sopenharmony_ci} 17162306a36Sopenharmony_ci 17262306a36Sopenharmony_cistatic int mdio_mux_mmioreg_remove(struct platform_device *pdev) 17362306a36Sopenharmony_ci{ 17462306a36Sopenharmony_ci struct mdio_mux_mmioreg_state *s = dev_get_platdata(&pdev->dev); 17562306a36Sopenharmony_ci 17662306a36Sopenharmony_ci mdio_mux_uninit(s->mux_handle); 17762306a36Sopenharmony_ci 17862306a36Sopenharmony_ci return 0; 17962306a36Sopenharmony_ci} 18062306a36Sopenharmony_ci 18162306a36Sopenharmony_cistatic const struct of_device_id mdio_mux_mmioreg_match[] = { 18262306a36Sopenharmony_ci { 18362306a36Sopenharmony_ci .compatible = "mdio-mux-mmioreg", 18462306a36Sopenharmony_ci }, 18562306a36Sopenharmony_ci {}, 18662306a36Sopenharmony_ci}; 18762306a36Sopenharmony_ciMODULE_DEVICE_TABLE(of, mdio_mux_mmioreg_match); 18862306a36Sopenharmony_ci 18962306a36Sopenharmony_cistatic struct platform_driver mdio_mux_mmioreg_driver = { 19062306a36Sopenharmony_ci .driver = { 19162306a36Sopenharmony_ci .name = "mdio-mux-mmioreg", 19262306a36Sopenharmony_ci .of_match_table = mdio_mux_mmioreg_match, 19362306a36Sopenharmony_ci }, 19462306a36Sopenharmony_ci .probe = mdio_mux_mmioreg_probe, 19562306a36Sopenharmony_ci .remove = mdio_mux_mmioreg_remove, 19662306a36Sopenharmony_ci}; 19762306a36Sopenharmony_ci 19862306a36Sopenharmony_cimodule_platform_driver(mdio_mux_mmioreg_driver); 19962306a36Sopenharmony_ci 20062306a36Sopenharmony_ciMODULE_AUTHOR("Timur Tabi <timur@freescale.com>"); 20162306a36Sopenharmony_ciMODULE_DESCRIPTION("Memory-mapped device MDIO MUX driver"); 20262306a36Sopenharmony_ciMODULE_LICENSE("GPL v2"); 203