1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | // |
3 | // Driver for the regulator based Ethernet Power Sourcing Equipment, without |
4 | // auto classification support. |
5 | // |
6 | // Copyright (c) 2022 Pengutronix, Oleksij Rempel <kernel@pengutronix.de> |
7 | // |
8 | |
9 | #include <linux/module.h> |
10 | #include <linux/of.h> |
11 | #include <linux/platform_device.h> |
12 | #include <linux/pse-pd/pse.h> |
13 | #include <linux/regulator/consumer.h> |
14 | |
15 | struct pse_reg_priv { |
16 | struct pse_controller_dev pcdev; |
17 | struct regulator *ps; /*power source */ |
18 | enum ethtool_podl_pse_admin_state admin_state; |
19 | }; |
20 | |
21 | static struct pse_reg_priv *to_pse_reg(struct pse_controller_dev *pcdev) |
22 | { |
23 | return container_of(pcdev, struct pse_reg_priv, pcdev); |
24 | } |
25 | |
26 | static int |
27 | pse_reg_ethtool_set_config(struct pse_controller_dev *pcdev, unsigned long id, |
28 | struct netlink_ext_ack *extack, |
29 | const struct pse_control_config *config) |
30 | { |
31 | struct pse_reg_priv *priv = to_pse_reg(pcdev); |
32 | int ret; |
33 | |
34 | if (priv->admin_state == config->admin_cotrol) |
35 | return 0; |
36 | |
37 | switch (config->admin_cotrol) { |
38 | case ETHTOOL_PODL_PSE_ADMIN_STATE_ENABLED: |
39 | ret = regulator_enable(regulator: priv->ps); |
40 | break; |
41 | case ETHTOOL_PODL_PSE_ADMIN_STATE_DISABLED: |
42 | ret = regulator_disable(regulator: priv->ps); |
43 | break; |
44 | default: |
45 | dev_err(pcdev->dev, "Unknown admin state %i\n" , |
46 | config->admin_cotrol); |
47 | ret = -ENOTSUPP; |
48 | } |
49 | |
50 | if (ret) |
51 | return ret; |
52 | |
53 | priv->admin_state = config->admin_cotrol; |
54 | |
55 | return 0; |
56 | } |
57 | |
58 | static int |
59 | pse_reg_ethtool_get_status(struct pse_controller_dev *pcdev, unsigned long id, |
60 | struct netlink_ext_ack *extack, |
61 | struct pse_control_status *status) |
62 | { |
63 | struct pse_reg_priv *priv = to_pse_reg(pcdev); |
64 | int ret; |
65 | |
66 | ret = regulator_is_enabled(regulator: priv->ps); |
67 | if (ret < 0) |
68 | return ret; |
69 | |
70 | if (!ret) |
71 | status->podl_pw_status = ETHTOOL_PODL_PSE_PW_D_STATUS_DISABLED; |
72 | else |
73 | status->podl_pw_status = |
74 | ETHTOOL_PODL_PSE_PW_D_STATUS_DELIVERING; |
75 | |
76 | status->podl_admin_state = priv->admin_state; |
77 | |
78 | return 0; |
79 | } |
80 | |
81 | static const struct pse_controller_ops pse_reg_ops = { |
82 | .ethtool_get_status = pse_reg_ethtool_get_status, |
83 | .ethtool_set_config = pse_reg_ethtool_set_config, |
84 | }; |
85 | |
86 | static int |
87 | pse_reg_probe(struct platform_device *pdev) |
88 | { |
89 | struct device *dev = &pdev->dev; |
90 | struct pse_reg_priv *priv; |
91 | int ret; |
92 | |
93 | priv = devm_kzalloc(dev: &pdev->dev, size: sizeof(*priv), GFP_KERNEL); |
94 | if (!priv) |
95 | return -ENOMEM; |
96 | |
97 | if (!pdev->dev.of_node) |
98 | return -ENOENT; |
99 | |
100 | priv->ps = devm_regulator_get_exclusive(dev, id: "pse" ); |
101 | if (IS_ERR(ptr: priv->ps)) |
102 | return dev_err_probe(dev, err: PTR_ERR(ptr: priv->ps), |
103 | fmt: "failed to get PSE regulator.\n" ); |
104 | |
105 | platform_set_drvdata(pdev, data: priv); |
106 | |
107 | ret = regulator_is_enabled(regulator: priv->ps); |
108 | if (ret < 0) |
109 | return ret; |
110 | |
111 | if (ret) |
112 | priv->admin_state = ETHTOOL_PODL_PSE_ADMIN_STATE_ENABLED; |
113 | else |
114 | priv->admin_state = ETHTOOL_PODL_PSE_ADMIN_STATE_DISABLED; |
115 | |
116 | priv->pcdev.owner = THIS_MODULE; |
117 | priv->pcdev.ops = &pse_reg_ops; |
118 | priv->pcdev.dev = dev; |
119 | ret = devm_pse_controller_register(dev, pcdev: &priv->pcdev); |
120 | if (ret) { |
121 | dev_err(dev, "failed to register PSE controller (%pe)\n" , |
122 | ERR_PTR(ret)); |
123 | return ret; |
124 | } |
125 | |
126 | return 0; |
127 | } |
128 | |
129 | static const __maybe_unused struct of_device_id pse_reg_of_match[] = { |
130 | { .compatible = "podl-pse-regulator" , }, |
131 | { }, |
132 | }; |
133 | MODULE_DEVICE_TABLE(of, pse_reg_of_match); |
134 | |
135 | static struct platform_driver pse_reg_driver = { |
136 | .probe = pse_reg_probe, |
137 | .driver = { |
138 | .name = "PSE regulator" , |
139 | .of_match_table = of_match_ptr(pse_reg_of_match), |
140 | }, |
141 | }; |
142 | module_platform_driver(pse_reg_driver); |
143 | |
144 | MODULE_AUTHOR("Oleksij Rempel <kernel@pengutronix.de>" ); |
145 | MODULE_DESCRIPTION("regulator based Ethernet Power Sourcing Equipment" ); |
146 | MODULE_LICENSE("GPL v2" ); |
147 | MODULE_ALIAS("platform:pse-regulator" ); |
148 | |