162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0+ 262306a36Sopenharmony_ci/* MDIO bus multiplexer using kernel multiplexer subsystem 362306a36Sopenharmony_ci * 462306a36Sopenharmony_ci * Copyright 2019 NXP 562306a36Sopenharmony_ci */ 662306a36Sopenharmony_ci 762306a36Sopenharmony_ci#include <linux/mdio-mux.h> 862306a36Sopenharmony_ci#include <linux/module.h> 962306a36Sopenharmony_ci#include <linux/mux/consumer.h> 1062306a36Sopenharmony_ci#include <linux/platform_device.h> 1162306a36Sopenharmony_ci 1262306a36Sopenharmony_cistruct mdio_mux_multiplexer_state { 1362306a36Sopenharmony_ci struct mux_control *muxc; 1462306a36Sopenharmony_ci bool do_deselect; 1562306a36Sopenharmony_ci void *mux_handle; 1662306a36Sopenharmony_ci}; 1762306a36Sopenharmony_ci 1862306a36Sopenharmony_ci/** 1962306a36Sopenharmony_ci * mdio_mux_multiplexer_switch_fn - This function is called by the mdio-mux 2062306a36Sopenharmony_ci * layer when it thinks the mdio bus 2162306a36Sopenharmony_ci * multiplexer needs to switch. 2262306a36Sopenharmony_ci * @current_child: current value of the mux register. 2362306a36Sopenharmony_ci * @desired_child: value of the 'reg' property of the target child MDIO node. 2462306a36Sopenharmony_ci * @data: Private data used by this switch_fn passed to mdio_mux_init function 2562306a36Sopenharmony_ci * via mdio_mux_init(.., .., .., .., data, ..). 2662306a36Sopenharmony_ci * 2762306a36Sopenharmony_ci * The first time this function is called, current_child == -1. 2862306a36Sopenharmony_ci * If current_child == desired_child, then the mux is already set to the 2962306a36Sopenharmony_ci * correct bus. 3062306a36Sopenharmony_ci */ 3162306a36Sopenharmony_cistatic int mdio_mux_multiplexer_switch_fn(int current_child, int desired_child, 3262306a36Sopenharmony_ci void *data) 3362306a36Sopenharmony_ci{ 3462306a36Sopenharmony_ci struct platform_device *pdev; 3562306a36Sopenharmony_ci struct mdio_mux_multiplexer_state *s; 3662306a36Sopenharmony_ci int ret = 0; 3762306a36Sopenharmony_ci 3862306a36Sopenharmony_ci pdev = (struct platform_device *)data; 3962306a36Sopenharmony_ci s = platform_get_drvdata(pdev); 4062306a36Sopenharmony_ci 4162306a36Sopenharmony_ci if (!(current_child ^ desired_child)) 4262306a36Sopenharmony_ci return 0; 4362306a36Sopenharmony_ci 4462306a36Sopenharmony_ci if (s->do_deselect) 4562306a36Sopenharmony_ci ret = mux_control_deselect(s->muxc); 4662306a36Sopenharmony_ci if (ret) { 4762306a36Sopenharmony_ci dev_err(&pdev->dev, "mux_control_deselect failed in %s: %d\n", 4862306a36Sopenharmony_ci __func__, ret); 4962306a36Sopenharmony_ci return ret; 5062306a36Sopenharmony_ci } 5162306a36Sopenharmony_ci 5262306a36Sopenharmony_ci ret = mux_control_select(s->muxc, desired_child); 5362306a36Sopenharmony_ci if (!ret) { 5462306a36Sopenharmony_ci dev_dbg(&pdev->dev, "%s %d -> %d\n", __func__, current_child, 5562306a36Sopenharmony_ci desired_child); 5662306a36Sopenharmony_ci s->do_deselect = true; 5762306a36Sopenharmony_ci } else { 5862306a36Sopenharmony_ci s->do_deselect = false; 5962306a36Sopenharmony_ci } 6062306a36Sopenharmony_ci 6162306a36Sopenharmony_ci return ret; 6262306a36Sopenharmony_ci} 6362306a36Sopenharmony_ci 6462306a36Sopenharmony_cistatic int mdio_mux_multiplexer_probe(struct platform_device *pdev) 6562306a36Sopenharmony_ci{ 6662306a36Sopenharmony_ci struct device *dev = &pdev->dev; 6762306a36Sopenharmony_ci struct mdio_mux_multiplexer_state *s; 6862306a36Sopenharmony_ci int ret = 0; 6962306a36Sopenharmony_ci 7062306a36Sopenharmony_ci s = devm_kzalloc(&pdev->dev, sizeof(*s), GFP_KERNEL); 7162306a36Sopenharmony_ci if (!s) 7262306a36Sopenharmony_ci return -ENOMEM; 7362306a36Sopenharmony_ci 7462306a36Sopenharmony_ci s->muxc = devm_mux_control_get(dev, NULL); 7562306a36Sopenharmony_ci if (IS_ERR(s->muxc)) 7662306a36Sopenharmony_ci return dev_err_probe(&pdev->dev, PTR_ERR(s->muxc), 7762306a36Sopenharmony_ci "Failed to get mux\n"); 7862306a36Sopenharmony_ci 7962306a36Sopenharmony_ci platform_set_drvdata(pdev, s); 8062306a36Sopenharmony_ci 8162306a36Sopenharmony_ci ret = mdio_mux_init(&pdev->dev, pdev->dev.of_node, 8262306a36Sopenharmony_ci mdio_mux_multiplexer_switch_fn, &s->mux_handle, 8362306a36Sopenharmony_ci pdev, NULL); 8462306a36Sopenharmony_ci 8562306a36Sopenharmony_ci return ret; 8662306a36Sopenharmony_ci} 8762306a36Sopenharmony_ci 8862306a36Sopenharmony_cistatic int mdio_mux_multiplexer_remove(struct platform_device *pdev) 8962306a36Sopenharmony_ci{ 9062306a36Sopenharmony_ci struct mdio_mux_multiplexer_state *s = platform_get_drvdata(pdev); 9162306a36Sopenharmony_ci 9262306a36Sopenharmony_ci mdio_mux_uninit(s->mux_handle); 9362306a36Sopenharmony_ci 9462306a36Sopenharmony_ci if (s->do_deselect) 9562306a36Sopenharmony_ci mux_control_deselect(s->muxc); 9662306a36Sopenharmony_ci 9762306a36Sopenharmony_ci return 0; 9862306a36Sopenharmony_ci} 9962306a36Sopenharmony_ci 10062306a36Sopenharmony_cistatic const struct of_device_id mdio_mux_multiplexer_match[] = { 10162306a36Sopenharmony_ci { .compatible = "mdio-mux-multiplexer", }, 10262306a36Sopenharmony_ci {}, 10362306a36Sopenharmony_ci}; 10462306a36Sopenharmony_ciMODULE_DEVICE_TABLE(of, mdio_mux_multiplexer_match); 10562306a36Sopenharmony_ci 10662306a36Sopenharmony_cistatic struct platform_driver mdio_mux_multiplexer_driver = { 10762306a36Sopenharmony_ci .driver = { 10862306a36Sopenharmony_ci .name = "mdio-mux-multiplexer", 10962306a36Sopenharmony_ci .of_match_table = mdio_mux_multiplexer_match, 11062306a36Sopenharmony_ci }, 11162306a36Sopenharmony_ci .probe = mdio_mux_multiplexer_probe, 11262306a36Sopenharmony_ci .remove = mdio_mux_multiplexer_remove, 11362306a36Sopenharmony_ci}; 11462306a36Sopenharmony_ci 11562306a36Sopenharmony_cimodule_platform_driver(mdio_mux_multiplexer_driver); 11662306a36Sopenharmony_ci 11762306a36Sopenharmony_ciMODULE_DESCRIPTION("MDIO bus multiplexer using kernel multiplexer subsystem"); 11862306a36Sopenharmony_ciMODULE_AUTHOR("Pankaj Bansal <pankaj.bansal@nxp.com>"); 11962306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 120