Files
linux/drivers/net/pse-pd/tps23881.c
Kory Maincent b710f18252 net: pse-pd: tps23881: Fix power on/off issue
[ Upstream commit 75221e9610 ]

An issue was present in the initial driver implementation. The driver
read the power status of all channels before toggling the bit of the
desired one. Using the power status register as a base value introduced
a problem, because only the bit corresponding to the concerned channel ID
should be set in the write-only power enable register. This led to cases
where disabling power for one channel also powered off other channels.

This patch removes the power status read and ensures the value is
limited to the bit matching the channel index of the PI.

Fixes: 20e6d190ff ("net: pse-pd: Add TI TPS23881 PSE controller driver")
Signed-off-by: Kory Maincent <kory.maincent@bootlin.com>
Acked-by: Oleksij Rempel <o.rempel@pengutronix.de>
Link: https://patch.msgid.link/20241220170400.291705-1-kory.maincent@bootlin.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
Signed-off-by: Sasha Levin <sashal@kernel.org>
2025-01-09 13:33:38 +01:00

837 lines
20 KiB
C

// SPDX-License-Identifier: GPL-2.0-only
/*
* Driver for the TI TPS23881 PoE PSE Controller driver (I2C bus)
*
* Copyright (c) 2023 Bootlin, Kory Maincent <kory.maincent@bootlin.com>
*/
#include <linux/bitfield.h>
#include <linux/delay.h>
#include <linux/firmware.h>
#include <linux/gpio/consumer.h>
#include <linux/i2c.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/pse-pd/pse.h>
#define TPS23881_MAX_CHANS 8
#define TPS23881_REG_PW_STATUS 0x10
#define TPS23881_REG_OP_MODE 0x12
#define TPS23881_OP_MODE_SEMIAUTO 0xaaaa
#define TPS23881_REG_DIS_EN 0x13
#define TPS23881_REG_DET_CLA_EN 0x14
#define TPS23881_REG_GEN_MASK 0x17
#define TPS23881_REG_NBITACC BIT(5)
#define TPS23881_REG_PW_EN 0x19
#define TPS23881_REG_PORT_MAP 0x26
#define TPS23881_REG_PORT_POWER 0x29
#define TPS23881_REG_POEPLUS 0x40
#define TPS23881_REG_TPON BIT(0)
#define TPS23881_REG_FWREV 0x41
#define TPS23881_REG_DEVID 0x43
#define TPS23881_REG_DEVID_MASK 0xF0
#define TPS23881_DEVICE_ID 0x02
#define TPS23881_REG_SRAM_CTRL 0x60
#define TPS23881_REG_SRAM_DATA 0x61
struct tps23881_port_desc {
u8 chan[2];
bool is_4p;
};
struct tps23881_priv {
struct i2c_client *client;
struct pse_controller_dev pcdev;
struct device_node *np;
struct tps23881_port_desc port[TPS23881_MAX_CHANS];
};
static struct tps23881_priv *to_tps23881_priv(struct pse_controller_dev *pcdev)
{
return container_of(pcdev, struct tps23881_priv, pcdev);
}
static int tps23881_pi_enable(struct pse_controller_dev *pcdev, int id)
{
struct tps23881_priv *priv = to_tps23881_priv(pcdev);
struct i2c_client *client = priv->client;
u8 chan;
u16 val;
int ret;
if (id >= TPS23881_MAX_CHANS)
return -ERANGE;
chan = priv->port[id].chan[0];
if (chan < 4)
val = BIT(chan);
else
val = BIT(chan + 4);
if (priv->port[id].is_4p) {
chan = priv->port[id].chan[1];
if (chan < 4)
val |= BIT(chan);
else
val |= BIT(chan + 4);
}
ret = i2c_smbus_write_word_data(client, TPS23881_REG_PW_EN, val);
if (ret)
return ret;
return 0;
}
static int tps23881_pi_disable(struct pse_controller_dev *pcdev, int id)
{
struct tps23881_priv *priv = to_tps23881_priv(pcdev);
struct i2c_client *client = priv->client;
u8 chan;
u16 val;
int ret;
if (id >= TPS23881_MAX_CHANS)
return -ERANGE;
chan = priv->port[id].chan[0];
if (chan < 4)
val = BIT(chan + 4);
else
val = BIT(chan + 8);
if (priv->port[id].is_4p) {
chan = priv->port[id].chan[1];
if (chan < 4)
val |= BIT(chan + 4);
else
val |= BIT(chan + 8);
}
ret = i2c_smbus_write_word_data(client, TPS23881_REG_PW_EN, val);
if (ret)
return ret;
return 0;
}
static int tps23881_pi_is_enabled(struct pse_controller_dev *pcdev, int id)
{
struct tps23881_priv *priv = to_tps23881_priv(pcdev);
struct i2c_client *client = priv->client;
bool enabled;
u8 chan;
int ret;
ret = i2c_smbus_read_word_data(client, TPS23881_REG_PW_STATUS);
if (ret < 0)
return ret;
chan = priv->port[id].chan[0];
if (chan < 4)
enabled = ret & BIT(chan);
else
enabled = ret & BIT(chan + 4);
if (priv->port[id].is_4p) {
chan = priv->port[id].chan[1];
if (chan < 4)
enabled &= !!(ret & BIT(chan));
else
enabled &= !!(ret & BIT(chan + 4));
}
/* Return enabled status only if both channel are on this state */
return enabled;
}
static int tps23881_ethtool_get_status(struct pse_controller_dev *pcdev,
unsigned long id,
struct netlink_ext_ack *extack,
struct pse_control_status *status)
{
struct tps23881_priv *priv = to_tps23881_priv(pcdev);
struct i2c_client *client = priv->client;
bool enabled, delivering;
u8 chan;
int ret;
ret = i2c_smbus_read_word_data(client, TPS23881_REG_PW_STATUS);
if (ret < 0)
return ret;
chan = priv->port[id].chan[0];
if (chan < 4) {
enabled = ret & BIT(chan);
delivering = ret & BIT(chan + 4);
} else {
enabled = ret & BIT(chan + 4);
delivering = ret & BIT(chan + 8);
}
if (priv->port[id].is_4p) {
chan = priv->port[id].chan[1];
if (chan < 4) {
enabled &= !!(ret & BIT(chan));
delivering &= !!(ret & BIT(chan + 4));
} else {
enabled &= !!(ret & BIT(chan + 4));
delivering &= !!(ret & BIT(chan + 8));
}
}
/* Return delivering status only if both channel are on this state */
if (delivering)
status->c33_pw_status = ETHTOOL_C33_PSE_PW_D_STATUS_DELIVERING;
else
status->c33_pw_status = ETHTOOL_C33_PSE_PW_D_STATUS_DISABLED;
/* Return enabled status only if both channel are on this state */
if (enabled)
status->c33_admin_state = ETHTOOL_C33_PSE_ADMIN_STATE_ENABLED;
else
status->c33_admin_state = ETHTOOL_C33_PSE_ADMIN_STATE_DISABLED;
return 0;
}
/* Parse managers subnode into a array of device node */
static int
tps23881_get_of_channels(struct tps23881_priv *priv,
struct device_node *chan_node[TPS23881_MAX_CHANS])
{
struct device_node *channels_node, *node;
int i, ret;
if (!priv->np)
return -EINVAL;
channels_node = of_find_node_by_name(priv->np, "channels");
if (!channels_node)
return -EINVAL;
for_each_child_of_node(channels_node, node) {
u32 chan_id;
if (!of_node_name_eq(node, "channel"))
continue;
ret = of_property_read_u32(node, "reg", &chan_id);
if (ret) {
ret = -EINVAL;
goto out;
}
if (chan_id >= TPS23881_MAX_CHANS || chan_node[chan_id]) {
dev_err(&priv->client->dev,
"wrong number of port (%d)\n", chan_id);
ret = -EINVAL;
goto out;
}
of_node_get(node);
chan_node[chan_id] = node;
}
of_node_put(channels_node);
return 0;
out:
for (i = 0; i < TPS23881_MAX_CHANS; i++) {
of_node_put(chan_node[i]);
chan_node[i] = NULL;
}
of_node_put(node);
of_node_put(channels_node);
return ret;
}
struct tps23881_port_matrix {
u8 pi_id;
u8 lgcl_chan[2];
u8 hw_chan[2];
bool is_4p;
bool exist;
};
static int
tps23881_match_channel(const struct pse_pi_pairset *pairset,
struct device_node *chan_node[TPS23881_MAX_CHANS])
{
int i;
/* Look on every channels */
for (i = 0; i < TPS23881_MAX_CHANS; i++) {
if (pairset->np == chan_node[i])
return i;
}
return -ENODEV;
}
static bool
tps23881_is_chan_free(struct tps23881_port_matrix port_matrix[TPS23881_MAX_CHANS],
int chan)
{
int i;
for (i = 0; i < TPS23881_MAX_CHANS; i++) {
if (port_matrix[i].exist &&
(port_matrix[i].hw_chan[0] == chan ||
port_matrix[i].hw_chan[1] == chan))
return false;
}
return true;
}
/* Fill port matrix with the matching channels */
static int
tps23881_match_port_matrix(struct pse_pi *pi, int pi_id,
struct device_node *chan_node[TPS23881_MAX_CHANS],
struct tps23881_port_matrix port_matrix[TPS23881_MAX_CHANS])
{
int ret;
if (!pi->pairset[0].np)
return 0;
ret = tps23881_match_channel(&pi->pairset[0], chan_node);
if (ret < 0)
return ret;
if (!tps23881_is_chan_free(port_matrix, ret)) {
pr_err("tps23881: channel %d already used\n", ret);
return -ENODEV;
}
port_matrix[pi_id].hw_chan[0] = ret;
port_matrix[pi_id].exist = true;
if (!pi->pairset[1].np)
return 0;
ret = tps23881_match_channel(&pi->pairset[1], chan_node);
if (ret < 0)
return ret;
if (!tps23881_is_chan_free(port_matrix, ret)) {
pr_err("tps23881: channel %d already used\n", ret);
return -ENODEV;
}
if (port_matrix[pi_id].hw_chan[0] / 4 != ret / 4) {
pr_err("tps23881: 4-pair PSE can only be set within the same 4 ports group");
return -ENODEV;
}
port_matrix[pi_id].hw_chan[1] = ret;
port_matrix[pi_id].is_4p = true;
return 0;
}
static int
tps23881_get_unused_chan(struct tps23881_port_matrix port_matrix[TPS23881_MAX_CHANS],
int port_cnt)
{
bool used;
int i, j;
for (i = 0; i < TPS23881_MAX_CHANS; i++) {
used = false;
for (j = 0; j < port_cnt; j++) {
if (port_matrix[j].hw_chan[0] == i) {
used = true;
break;
}
if (port_matrix[j].is_4p &&
port_matrix[j].hw_chan[1] == i) {
used = true;
break;
}
}
if (!used)
return i;
}
return -ENODEV;
}
/* Sort the port matrix to following particular hardware ports matrix
* specification of the tps23881. The device has two 4-ports groups and
* each 4-pair powered device has to be configured to use two consecutive
* logical channel in each 4 ports group (1 and 2 or 3 and 4). Also the
* hardware matrix has to be fully configured even with unused chan to be
* valid.
*/
static int
tps23881_sort_port_matrix(struct tps23881_port_matrix port_matrix[TPS23881_MAX_CHANS])
{
struct tps23881_port_matrix tmp_port_matrix[TPS23881_MAX_CHANS] = {0};
int i, ret, port_cnt = 0, cnt_4ch_grp1 = 0, cnt_4ch_grp2 = 4;
/* Configure 4p port matrix */
for (i = 0; i < TPS23881_MAX_CHANS; i++) {
int *cnt;
if (!port_matrix[i].exist || !port_matrix[i].is_4p)
continue;
if (port_matrix[i].hw_chan[0] < 4)
cnt = &cnt_4ch_grp1;
else
cnt = &cnt_4ch_grp2;
tmp_port_matrix[port_cnt].exist = true;
tmp_port_matrix[port_cnt].is_4p = true;
tmp_port_matrix[port_cnt].pi_id = i;
tmp_port_matrix[port_cnt].hw_chan[0] = port_matrix[i].hw_chan[0];
tmp_port_matrix[port_cnt].hw_chan[1] = port_matrix[i].hw_chan[1];
/* 4-pair ports have to be configured with consecutive
* logical channels 0 and 1, 2 and 3.
*/
tmp_port_matrix[port_cnt].lgcl_chan[0] = (*cnt)++;
tmp_port_matrix[port_cnt].lgcl_chan[1] = (*cnt)++;
port_cnt++;
}
/* Configure 2p port matrix */
for (i = 0; i < TPS23881_MAX_CHANS; i++) {
int *cnt;
if (!port_matrix[i].exist || port_matrix[i].is_4p)
continue;
if (port_matrix[i].hw_chan[0] < 4)
cnt = &cnt_4ch_grp1;
else
cnt = &cnt_4ch_grp2;
tmp_port_matrix[port_cnt].exist = true;
tmp_port_matrix[port_cnt].pi_id = i;
tmp_port_matrix[port_cnt].lgcl_chan[0] = (*cnt)++;
tmp_port_matrix[port_cnt].hw_chan[0] = port_matrix[i].hw_chan[0];
port_cnt++;
}
/* Complete the rest of the first 4 port group matrix even if
* channels are unused
*/
while (cnt_4ch_grp1 < 4) {
ret = tps23881_get_unused_chan(tmp_port_matrix, port_cnt);
if (ret < 0) {
pr_err("tps23881: port matrix issue, no chan available\n");
return ret;
}
if (port_cnt >= TPS23881_MAX_CHANS) {
pr_err("tps23881: wrong number of channels\n");
return -ENODEV;
}
tmp_port_matrix[port_cnt].lgcl_chan[0] = cnt_4ch_grp1;
tmp_port_matrix[port_cnt].hw_chan[0] = ret;
cnt_4ch_grp1++;
port_cnt++;
}
/* Complete the rest of the second 4 port group matrix even if
* channels are unused
*/
while (cnt_4ch_grp2 < 8) {
ret = tps23881_get_unused_chan(tmp_port_matrix, port_cnt);
if (ret < 0) {
pr_err("tps23881: port matrix issue, no chan available\n");
return -ENODEV;
}
if (port_cnt >= TPS23881_MAX_CHANS) {
pr_err("tps23881: wrong number of channels\n");
return -ENODEV;
}
tmp_port_matrix[port_cnt].lgcl_chan[0] = cnt_4ch_grp2;
tmp_port_matrix[port_cnt].hw_chan[0] = ret;
cnt_4ch_grp2++;
port_cnt++;
}
memcpy(port_matrix, tmp_port_matrix, sizeof(tmp_port_matrix));
return port_cnt;
}
/* Write port matrix to the hardware port matrix and the software port
* matrix.
*/
static int
tps23881_write_port_matrix(struct tps23881_priv *priv,
struct tps23881_port_matrix port_matrix[TPS23881_MAX_CHANS],
int port_cnt)
{
struct i2c_client *client = priv->client;
u8 pi_id, lgcl_chan, hw_chan;
u16 val = 0;
int i, ret;
for (i = 0; i < port_cnt; i++) {
pi_id = port_matrix[i].pi_id;
lgcl_chan = port_matrix[i].lgcl_chan[0];
hw_chan = port_matrix[i].hw_chan[0] % 4;
/* Set software port matrix for existing ports */
if (port_matrix[i].exist)
priv->port[pi_id].chan[0] = lgcl_chan;
/* Set hardware port matrix for all ports */
val |= hw_chan << (lgcl_chan * 2);
if (!port_matrix[i].is_4p)
continue;
lgcl_chan = port_matrix[i].lgcl_chan[1];
hw_chan = port_matrix[i].hw_chan[1] % 4;
/* Set software port matrix for existing ports */
if (port_matrix[i].exist) {
priv->port[pi_id].is_4p = true;
priv->port[pi_id].chan[1] = lgcl_chan;
}
/* Set hardware port matrix for all ports */
val |= hw_chan << (lgcl_chan * 2);
}
/* Write hardware ports matrix */
ret = i2c_smbus_write_word_data(client, TPS23881_REG_PORT_MAP, val);
if (ret)
return ret;
return 0;
}
static int
tps23881_set_ports_conf(struct tps23881_priv *priv,
struct tps23881_port_matrix port_matrix[TPS23881_MAX_CHANS])
{
struct i2c_client *client = priv->client;
int i, ret;
u16 val;
/* Set operating mode */
ret = i2c_smbus_write_word_data(client, TPS23881_REG_OP_MODE,
TPS23881_OP_MODE_SEMIAUTO);
if (ret)
return ret;
/* Disable DC disconnect */
ret = i2c_smbus_write_word_data(client, TPS23881_REG_DIS_EN, 0x0);
if (ret)
return ret;
/* Set port power allocation */
val = 0;
for (i = 0; i < TPS23881_MAX_CHANS; i++) {
if (!port_matrix[i].exist)
continue;
if (port_matrix[i].is_4p)
val |= 0xf << ((port_matrix[i].lgcl_chan[0] / 2) * 4);
else
val |= 0x3 << ((port_matrix[i].lgcl_chan[0] / 2) * 4);
}
ret = i2c_smbus_write_word_data(client, TPS23881_REG_PORT_POWER, val);
if (ret)
return ret;
/* Enable detection and classification */
val = 0;
for (i = 0; i < TPS23881_MAX_CHANS; i++) {
if (!port_matrix[i].exist)
continue;
val |= BIT(port_matrix[i].lgcl_chan[0]) |
BIT(port_matrix[i].lgcl_chan[0] + 4);
if (port_matrix[i].is_4p)
val |= BIT(port_matrix[i].lgcl_chan[1]) |
BIT(port_matrix[i].lgcl_chan[1] + 4);
}
ret = i2c_smbus_write_word_data(client, TPS23881_REG_DET_CLA_EN, val);
if (ret)
return ret;
return 0;
}
static int
tps23881_set_ports_matrix(struct tps23881_priv *priv,
struct device_node *chan_node[TPS23881_MAX_CHANS])
{
struct tps23881_port_matrix port_matrix[TPS23881_MAX_CHANS] = {0};
int i, ret;
/* Update with values for every PSE PIs */
for (i = 0; i < TPS23881_MAX_CHANS; i++) {
ret = tps23881_match_port_matrix(&priv->pcdev.pi[i], i,
chan_node, port_matrix);
if (ret)
return ret;
}
ret = tps23881_sort_port_matrix(port_matrix);
if (ret < 0)
return ret;
ret = tps23881_write_port_matrix(priv, port_matrix, ret);
if (ret)
return ret;
ret = tps23881_set_ports_conf(priv, port_matrix);
if (ret)
return ret;
return 0;
}
static int tps23881_setup_pi_matrix(struct pse_controller_dev *pcdev)
{
struct device_node *chan_node[TPS23881_MAX_CHANS] = {NULL};
struct tps23881_priv *priv = to_tps23881_priv(pcdev);
int ret, i;
ret = tps23881_get_of_channels(priv, chan_node);
if (ret < 0) {
dev_warn(&priv->client->dev,
"Unable to parse port-matrix, default matrix will be used\n");
return 0;
}
ret = tps23881_set_ports_matrix(priv, chan_node);
for (i = 0; i < TPS23881_MAX_CHANS; i++)
of_node_put(chan_node[i]);
return ret;
}
static const struct pse_controller_ops tps23881_ops = {
.setup_pi_matrix = tps23881_setup_pi_matrix,
.pi_enable = tps23881_pi_enable,
.pi_disable = tps23881_pi_disable,
.pi_is_enabled = tps23881_pi_is_enabled,
.ethtool_get_status = tps23881_ethtool_get_status,
};
static const char fw_parity_name[] = "ti/tps23881/tps23881-parity-14.bin";
static const char fw_sram_name[] = "ti/tps23881/tps23881-sram-14.bin";
struct tps23881_fw_conf {
u8 reg;
u8 val;
};
static const struct tps23881_fw_conf tps23881_fw_parity_conf[] = {
{.reg = 0x60, .val = 0x01},
{.reg = 0x62, .val = 0x00},
{.reg = 0x63, .val = 0x80},
{.reg = 0x60, .val = 0xC4},
{.reg = 0x1D, .val = 0xBC},
{.reg = 0xD7, .val = 0x02},
{.reg = 0x91, .val = 0x00},
{.reg = 0x90, .val = 0x00},
{.reg = 0xD7, .val = 0x00},
{.reg = 0x1D, .val = 0x00},
{ /* sentinel */ }
};
static const struct tps23881_fw_conf tps23881_fw_sram_conf[] = {
{.reg = 0x60, .val = 0xC5},
{.reg = 0x62, .val = 0x00},
{.reg = 0x63, .val = 0x80},
{.reg = 0x60, .val = 0xC0},
{.reg = 0x1D, .val = 0xBC},
{.reg = 0xD7, .val = 0x02},
{.reg = 0x91, .val = 0x00},
{.reg = 0x90, .val = 0x00},
{.reg = 0xD7, .val = 0x00},
{.reg = 0x1D, .val = 0x00},
{ /* sentinel */ }
};
static int tps23881_flash_sram_fw_part(struct i2c_client *client,
const char *fw_name,
const struct tps23881_fw_conf *fw_conf)
{
const struct firmware *fw = NULL;
int i, ret;
ret = request_firmware(&fw, fw_name, &client->dev);
if (ret)
return ret;
dev_dbg(&client->dev, "Flashing %s\n", fw_name);
/* Prepare device for RAM download */
while (fw_conf->reg) {
ret = i2c_smbus_write_byte_data(client, fw_conf->reg,
fw_conf->val);
if (ret)
goto out;
fw_conf++;
}
/* Flash the firmware file */
for (i = 0; i < fw->size; i++) {
ret = i2c_smbus_write_byte_data(client,
TPS23881_REG_SRAM_DATA,
fw->data[i]);
if (ret)
goto out;
}
out:
release_firmware(fw);
return ret;
}
static int tps23881_flash_sram_fw(struct i2c_client *client)
{
int ret;
ret = tps23881_flash_sram_fw_part(client, fw_parity_name,
tps23881_fw_parity_conf);
if (ret)
return ret;
ret = tps23881_flash_sram_fw_part(client, fw_sram_name,
tps23881_fw_sram_conf);
if (ret)
return ret;
ret = i2c_smbus_write_byte_data(client, TPS23881_REG_SRAM_CTRL, 0x18);
if (ret)
return ret;
mdelay(12);
return 0;
}
static int tps23881_i2c_probe(struct i2c_client *client)
{
struct device *dev = &client->dev;
struct tps23881_priv *priv;
struct gpio_desc *reset;
int ret;
u8 val;
if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
dev_err(dev, "i2c check functionality failed\n");
return -ENXIO;
}
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
reset = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH);
if (IS_ERR(reset))
return dev_err_probe(&client->dev, PTR_ERR(reset), "Failed to get reset GPIO\n");
if (reset) {
/* TPS23880 datasheet (Rev G) indicates minimum reset pulse is 5us */
usleep_range(5, 10);
gpiod_set_value_cansleep(reset, 0); /* De-assert reset */
/* TPS23880 datasheet indicates the minimum time after power on reset
* should be 20ms, but the document describing how to load SRAM ("How
* to Load TPS2388x SRAM and Parity Code over I2C" (Rev E))
* indicates we should delay that programming by at least 50ms. So
* we'll wait the entire 50ms here to ensure we're safe to go to the
* SRAM loading proceedure.
*/
msleep(50);
}
ret = i2c_smbus_read_byte_data(client, TPS23881_REG_DEVID);
if (ret < 0)
return ret;
if (FIELD_GET(TPS23881_REG_DEVID_MASK, ret) != TPS23881_DEVICE_ID) {
dev_err(dev, "Wrong device ID\n");
return -ENXIO;
}
ret = tps23881_flash_sram_fw(client);
if (ret < 0)
return ret;
ret = i2c_smbus_read_byte_data(client, TPS23881_REG_FWREV);
if (ret < 0)
return ret;
dev_info(&client->dev, "Firmware revision 0x%x\n", ret);
/* Set configuration B, 16 bit access on a single device address */
ret = i2c_smbus_read_byte_data(client, TPS23881_REG_GEN_MASK);
if (ret < 0)
return ret;
val = ret | TPS23881_REG_NBITACC;
ret = i2c_smbus_write_byte_data(client, TPS23881_REG_GEN_MASK, val);
if (ret)
return ret;
priv->client = client;
i2c_set_clientdata(client, priv);
priv->np = dev->of_node;
priv->pcdev.owner = THIS_MODULE;
priv->pcdev.ops = &tps23881_ops;
priv->pcdev.dev = dev;
priv->pcdev.types = ETHTOOL_PSE_C33;
priv->pcdev.nr_lines = TPS23881_MAX_CHANS;
ret = devm_pse_controller_register(dev, &priv->pcdev);
if (ret) {
return dev_err_probe(dev, ret,
"failed to register PSE controller\n");
}
return ret;
}
static const struct i2c_device_id tps23881_id[] = {
{ "tps23881" },
{ }
};
MODULE_DEVICE_TABLE(i2c, tps23881_id);
static const struct of_device_id tps23881_of_match[] = {
{ .compatible = "ti,tps23881", },
{ },
};
MODULE_DEVICE_TABLE(of, tps23881_of_match);
static struct i2c_driver tps23881_driver = {
.probe = tps23881_i2c_probe,
.id_table = tps23881_id,
.driver = {
.name = "tps23881",
.of_match_table = tps23881_of_match,
},
};
module_i2c_driver(tps23881_driver);
MODULE_AUTHOR("Kory Maincent <kory.maincent@bootlin.com>");
MODULE_DESCRIPTION("TI TPS23881 PoE PSE Controller driver");
MODULE_LICENSE("GPL");