[PATCH 4/5] ath79: mikrotik: add poe driver
Oskari Lemmela
oskari at lemmela.net
Tue Jan 25 22:38:44 PST 2022
Add hwmon based driver for mikrotik POE controllers.
Signed-off-by: Oskari Lemmela <oskari at lemmela.net>
---
.../linux/ath79/files/drivers/hwmon/rbpoe.c | 256 ++++++++++++++
.../linux/ath79/files/drivers/hwmon/rbpoe.h | 25 ++
.../ath79/files/drivers/hwmon/rbpoeport.c | 311 ++++++++++++++++++
target/linux/ath79/mikrotik/config-default | 3 +
.../902-hwmon-support-for-mikrotik-poe.patch | 51 +++
5 files changed, 646 insertions(+)
create mode 100644 target/linux/ath79/files/drivers/hwmon/rbpoe.c
create mode 100644 target/linux/ath79/files/drivers/hwmon/rbpoe.h
create mode 100644 target/linux/ath79/files/drivers/hwmon/rbpoeport.c
create mode 100644 target/linux/ath79/patches-5.10/902-hwmon-support-for-mikrotik-poe.patch
diff --git a/target/linux/ath79/files/drivers/hwmon/rbpoe.c b/target/linux/ath79/files/drivers/hwmon/rbpoe.c
new file mode 100644
index 0000000000..fb5de6e6d7
--- /dev/null
+++ b/target/linux/ath79/files/drivers/hwmon/rbpoe.c
@@ -0,0 +1,256 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+/*
+ * Mikrotik POE driver
+ *
+ * Based on https://github.com/adron-s/mtpoe_ctrl
+ */
+
+#include <linux/crc8.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/hwmon.h>
+#include <linux/hwmon-sysfs.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/jiffies.h>
+#include <linux/spi/spi.h>
+#include <linux/of_platform.h>
+#include <linux/version.h>
+
+#include "rbpoe.h"
+
+DECLARE_CRC8_TABLE(rbpoe_crc_table);
+
+#define MAX_PORTS 4
+
+int rb_poe_get_port_idx(struct rb_poe_data *poe, int reg)
+{
+ if (poe->data->reverse)
+ return (MAX_PORTS-reg);
+ else
+ return reg-1;
+}
+EXPORT_SYMBOL(rb_poe_get_port_idx);
+
+int rb_poe_write_cmd(struct rb_poe_data *poe, u16 *resp, u8 cmd, u8 arg1, u8 arg2)
+{
+ int ret;
+ u8 tx[4];
+ u8 rx[6];
+ u8 crc, retries;
+
+ struct spi_transfer xfers[] = {
+ {
+ .tx_buf = tx,
+ .len = 4,
+ .delay.value = 10,
+ .delay.unit = SPI_DELAY_UNIT_USECS,
+ },
+ {
+ .rx_buf = rx,
+ .len = 6,
+ },
+ };
+
+ mutex_lock(&poe->lock);
+
+ tx[0] = cmd;
+ tx[1] = arg1;
+ tx[2] = arg2;
+ tx[3] = crc8(rbpoe_crc_table, tx, 3, 0);
+
+ for (retries = 0; retries < MAX_RETRIES; retries++) {
+ ret = spi_sync_transfer(poe->spi, xfers, ARRAY_SIZE(xfers));
+ if (ret < 0) {
+ dev_err(&poe->spi->dev, "SPI transfer error");
+ goto out;
+ }
+
+ if (rx[1] != cmd) {
+ ndelay(13);
+ continue;
+ }
+
+ crc = crc8(rbpoe_crc_table, rx+1, 3, 0);
+
+ if (rx[4] != crc && rx[5] != crc)
+ continue;
+
+ resp[0] = rx[2] << 8 | rx[3];
+ goto out;
+ }
+out:
+ mutex_unlock(&poe->lock);
+ return ret;
+}
+EXPORT_SYMBOL(rb_poe_write_cmd);
+
+static int rb_poe_read_version(struct rb_poe_data *data)
+{
+ int ret;
+ u16 vers;
+
+ ret = rb_poe_write_cmd(data, &vers, 0x41, 0, 0);
+ if (ret < 0)
+ return ret;
+ return vers;
+}
+
+int rb_poe_read_voltage(struct rb_poe_data *data)
+{
+ int ret;
+ u16 val;
+
+ ret = rb_poe_write_cmd(data, &val, 0x42, 0, 0);
+ if (ret < 0)
+ return ret;
+ return val * data->data->volt_lsb;
+}
+EXPORT_SYMBOL(rb_poe_read_voltage);
+
+static int rb_poe_read_temperature(struct rb_poe_data *data)
+{
+ int ret;
+ u16 val;
+
+ ret = rb_poe_write_cmd(data, &val, 0x43, 0, 0);
+ if (ret < 0)
+ return ret;
+ return (val * data->data->temp_lsb) - data->data->temp_offset;
+}
+
+/* sysfs attributes */
+static ssize_t temp_input_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct rb_poe_data *data = dev_get_drvdata(dev);
+ int val;
+
+ if (IS_ERR(data))
+ return -ENODATA;
+
+ val = rb_poe_read_temperature(data);
+ if (val < 0)
+ return -ENODATA;
+
+ return sprintf(buf, "%d\n", val);
+}
+
+static ssize_t in_input_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct rb_poe_data *data = dev_get_drvdata(dev);
+ int val;
+
+ if (IS_ERR(data))
+ return -ENODATA;
+
+ val = rb_poe_read_voltage(data);
+ if (val < 0)
+ return -ENODATA;
+
+ return sprintf(buf, "%d\n", val);
+}
+
+static SENSOR_DEVICE_ATTR_RO(temp1_input, temp_input, 0);
+static SENSOR_DEVICE_ATTR_RO(in0_input, in_input, 4);
+
+static struct attribute *rb_poe_attrs[] = {
+ &sensor_dev_attr_temp1_input.dev_attr.attr,
+ &sensor_dev_attr_in0_input.dev_attr.attr,
+ NULL
+};
+
+ATTRIBUTE_GROUPS(rb_poe);
+
+static int rb_poe_probe(struct spi_device *spi)
+{
+ struct device *dev = &spi->dev;
+ struct device *hwmon_dev;
+ struct rb_poe_data *data;
+ int ret;
+
+ data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ data->data = (struct rb_poe_model *)of_device_get_match_data(dev);
+ data->spi = spi;
+
+ spi->mode = SPI_MODE_0;
+ ret = spi_setup(spi);
+ if (ret)
+ return ret;
+
+ crc8_populate_lsb(rbpoe_crc_table, 0x8C);
+ dev_set_drvdata(dev, data);
+ mutex_init(&data->lock);
+
+ ret = rb_poe_read_version(data);
+ if (ret < 0)
+ return ret;
+
+ dev_info(dev, "firmware: %d.%d", ret & 0xff, ret >> 8);
+
+ ret = of_platform_populate(dev->of_node, NULL, NULL, dev);
+ if (ret < 0) {
+ dev_err(dev, "failed to populate DT children\n");
+ return ret;
+ }
+
+ hwmon_dev = devm_hwmon_device_register_with_groups(dev, "rbpoe", data, rb_poe_groups);
+
+ if (IS_ERR(hwmon_dev))
+ dev_dbg(dev, "unable to register hwmon device\n");
+
+ return PTR_ERR_OR_ZERO(hwmon_dev);
+}
+
+static const struct rb_poe_model v2_data = {
+ .reverse = 1,
+ .volt_lsb = 35,
+ .temp_lsb = 1000,
+ .temp_offset = 273000,
+};
+
+/*
+ * ATSAMD20J15 temperature sensor factory calibration is done at 25C 667mV with Vddana=3.3V
+ * Typical sensor slope 2.4mV/C. 667mV/2.4mV/C = 277.917C - 25C = 253.917C
+ * Measured Vddana is 3.29V, reduce slope a bit and tune calibration vdd.
+ * 680mV/2.375mV/C = 286.315C - 25C = 261.315C
+ * 1000/2.375 = 421
+ */
+static const struct rb_poe_model v3_data = {
+ .reverse = 0,
+ .volt_lsb = 10,
+ .temp_lsb = 421,
+ .temp_offset = 261315,
+};
+
+static const struct of_device_id rb_poe_dt_match[] = {
+ {
+ .compatible = "mikrotik,poe-v2",
+ .data = (void *)&v2_data,
+ }, {
+ .compatible = "mikrotik,poe-v3",
+ .data = (void *)&v3_data,
+ }, { /* sentinel */ },
+};
+MODULE_DEVICE_TABLE(of, rb_poe_dt_match);
+
+static struct spi_driver rb_poe_driver = {
+ .probe = rb_poe_probe,
+ .driver = {
+ .name = "rb-poe-driver",
+ .bus = &spi_bus_type,
+ .of_match_table = of_match_ptr(rb_poe_dt_match),
+ },
+};
+
+module_spi_driver(rb_poe_driver);
+
+MODULE_AUTHOR("Oskari Lemmela <oskari at lemmela.net>");
+MODULE_DESCRIPTION("Mikrotik POE driver");
+MODULE_LICENSE("GPL");
diff --git a/target/linux/ath79/files/drivers/hwmon/rbpoe.h b/target/linux/ath79/files/drivers/hwmon/rbpoe.h
new file mode 100644
index 0000000000..af1f59329f
--- /dev/null
+++ b/target/linux/ath79/files/drivers/hwmon/rbpoe.h
@@ -0,0 +1,25 @@
+/* SPDX-License-Identifier: GPL-2.0-only
+ *
+ * POE driver for the MikroTik RouterBoard series
+ */
+#include <linux/spi/spi.h>
+
+#define MAX_RETRIES 10
+
+struct rb_poe_model {
+ bool reverse;
+ u8 volt_lsb;
+ u16 temp_lsb;
+ u32 temp_offset;
+};
+
+struct rb_poe_data {
+ struct spi_device *spi;
+ struct rb_poe_model *data;
+
+ struct mutex lock;
+};
+
+int rb_poe_write_cmd(struct rb_poe_data *data, u16 *resp, u8 cmd, u8 arg1, u8 arg2);
+int rb_poe_get_port_idx(struct rb_poe_data *data, int reg);
+int rb_poe_read_voltage(struct rb_poe_data *data);
diff --git a/target/linux/ath79/files/drivers/hwmon/rbpoeport.c b/target/linux/ath79/files/drivers/hwmon/rbpoeport.c
new file mode 100644
index 0000000000..1b9852a0e7
--- /dev/null
+++ b/target/linux/ath79/files/drivers/hwmon/rbpoeport.c
@@ -0,0 +1,311 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+/*
+ * Mikrotik POE port driver
+ */
+
+#include <linux/err.h>
+#include <linux/delay.h>
+#include <linux/hwmon.h>
+#include <linux/hwmon-sysfs.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/jiffies.h>
+#include <linux/spi/spi.h>
+#include <linux/platform_device.h>
+#include <linux/of_platform.h>
+
+#include "rbpoe.h"
+
+#define PORT_DISABLED 0x0
+#define PORT_FORCE 0x1
+#define PORT_ENABLED 0x2
+
+struct poeport_data {
+ struct rb_poe_data *poe;
+ struct device *dev;
+
+ u8 state;
+ u8 force;
+ u32 reg;
+};
+
+static int read_state(struct poeport_data *port)
+{
+ int ret;
+ u16 status;
+
+ ret = rb_poe_write_cmd(port->poe, &status, 0x45, 0, 0);
+ if (ret < 0)
+ return ret;
+ return status >> rb_poe_get_port_idx(port->poe, port->reg)*4 & 0xF;
+}
+
+static int write_state(struct poeport_data *port, u8 state, u8 force)
+{
+ int ret;
+ u16 status;
+
+ if (state)
+ if (force)
+ state = PORT_FORCE;
+ else
+ state = PORT_ENABLED;
+ else
+ state = PORT_DISABLED;
+
+ ret = rb_poe_write_cmd(port->poe, &status, 0x44, port->reg, state);
+ if (ret < 0) {
+ usleep_range(100, 150);
+ ret = rb_poe_write_cmd(port->poe, &status, 0x44, port->reg, state);
+ }
+ return ret;
+}
+
+static int read_current(struct poeport_data *port)
+{
+ int ret;
+ u16 curr;
+
+ ret = rb_poe_write_cmd(port->poe, &curr, 0x58+port->reg, 0, 0);
+ if (ret < 0)
+ return ret;
+ return curr;
+}
+
+/* sysfs attributes */
+static ssize_t in_input_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct poeport_data *data = dev_get_drvdata(dev);
+ int state, curr;
+
+ if (IS_ERR(data))
+ return PTR_ERR(data);
+
+ state = read_state(data);
+ if (state < 0 || state == PORT_DISABLED)
+ return -ENODATA;
+
+ curr = read_current(data);
+ if (curr < 0 || (state == PORT_ENABLED && curr >> 15))
+ return -ENODATA;
+
+ return sprintf(buf, "%d\n", rb_poe_read_voltage(data->poe));
+}
+
+static ssize_t force_enable_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct poeport_data *data = dev_get_drvdata(dev);
+
+ if (IS_ERR(data))
+ return PTR_ERR(data);
+
+ return sprintf(buf, "%d\n", data->force);
+}
+
+static ssize_t force_enable_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf,
+ size_t count)
+{
+ struct poeport_data *data = dev_get_drvdata(dev);
+ int ret;
+ int state;
+
+ ret = kstrtoint(buf, 0, &state);
+ if (ret)
+ return ret;
+
+ data->force = state;
+ ret = write_state(data, data->state, state);
+ if (ret)
+ return ret;
+ return count;
+}
+
+static ssize_t port_state_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct poeport_data *data = dev_get_drvdata(dev);
+ int curr;
+
+ if (IS_ERR(data))
+ return PTR_ERR(data);
+
+ if (!data->state)
+ return sprintf(buf, "disabled\n");
+
+ curr = read_current(data);
+ if (curr == 0x800A)
+ return sprintf(buf, "short circuit\n");
+ if (!data->force && curr >> 15)
+ return sprintf(buf, "searching\n");
+ if (curr < 3)
+ return sprintf(buf, "no load\n");
+
+ return sprintf(buf, "delivering\n");
+
+}
+
+static ssize_t in_enable_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct poeport_data *data = dev_get_drvdata(dev);
+
+ if (IS_ERR(data))
+ return PTR_ERR(data);
+
+ return sprintf(buf, "%d\n", data->state);
+}
+
+static ssize_t in_enable_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf,
+ size_t count)
+{
+ struct poeport_data *data = dev_get_drvdata(dev);
+ int ret;
+ int state;
+
+ ret = kstrtoint(buf, 0, &state);
+ if (ret)
+ return ret;
+
+ data->state = state;
+ ret = write_state(data, state, data->force);
+ if (ret)
+ return ret;
+
+ return count;
+}
+
+static ssize_t curr_input_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct poeport_data *data = dev_get_drvdata(dev);
+ int curr;
+
+ if (IS_ERR(data))
+ return PTR_ERR(data);
+
+ curr = read_current(data);
+ if (curr < 0 || curr >> 15)
+ return -ENODATA;
+
+ return sprintf(buf, "%d\n", curr);
+}
+
+static ssize_t power_input_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct poeport_data *data = dev_get_drvdata(dev);
+ int volt, curr;
+
+ if (IS_ERR(data))
+ return PTR_ERR(data);
+
+ curr = read_current(data);
+ if (curr < 0 || curr >> 15)
+ return -ENODATA;
+
+ volt = rb_poe_read_voltage(data->poe);
+ if (volt < 0)
+ return -ENODATA;
+
+ return sprintf(buf, "%d\n", curr*volt);
+}
+
+static SENSOR_DEVICE_ATTR_RO(curr1_input, curr_input, 0);
+static SENSOR_DEVICE_ATTR_RO(power1_input, power_input, 0);
+static SENSOR_DEVICE_ATTR_RW(in1_enable, in_enable, 0);
+static SENSOR_DEVICE_ATTR_RO(in1_input, in_input, 0);
+static SENSOR_DEVICE_ATTR_RW(force_enable, force_enable, 0);
+static SENSOR_DEVICE_ATTR_RO(port_state, port_state, 0);
+
+static struct attribute *rb_poeport_attrs[] = {
+ &sensor_dev_attr_curr1_input.dev_attr.attr,
+ &sensor_dev_attr_power1_input.dev_attr.attr,
+ &sensor_dev_attr_in1_enable.dev_attr.attr,
+ &sensor_dev_attr_in1_input.dev_attr.attr,
+ &sensor_dev_attr_force_enable.dev_attr.attr,
+ &sensor_dev_attr_port_state.dev_attr.attr,
+ NULL
+};
+ATTRIBUTE_GROUPS(rb_poeport);
+
+static int rb_poeport_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct device *hwmon_dev;
+ struct poeport_data *data;
+ const char *label;
+ int ret, val;
+
+ data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ data->dev = dev;
+
+ ret = of_property_read_u32(dev->of_node, "reg", &val);
+ if (ret < 0) {
+ dev_err(dev, "missing 'reg' property (%d)\n", ret);
+ return ret;
+ }
+
+ data->reg = val;
+
+ if (of_property_read_string(dev->of_node, "label", &label) < 0)
+ label = "poeport";
+
+ if (!dev->parent) {
+ dev_err(dev, "no ctrl device\n");
+ return -ENODEV;
+ }
+
+ data->poe = dev_get_drvdata(dev->parent);
+
+ switch (read_state(data)) {
+ case PORT_FORCE:
+ data->force = 1;
+ data->state = 1;
+ break;
+ case PORT_ENABLED:
+ data->force = 0;
+ data->state = 1;
+ break;
+ case PORT_DISABLED:
+ data->state = 0;
+ data->force = 0;
+ }
+
+ hwmon_dev = devm_hwmon_device_register_with_groups(dev, label, data, rb_poeport_groups);
+
+ if (IS_ERR(hwmon_dev))
+ dev_dbg(dev, "unable to register hwmon device\n");
+
+ return PTR_ERR_OR_ZERO(hwmon_dev);
+}
+
+static const struct of_device_id rb_poeport_of_match[] = {
+ { .compatible = "mikrotik,poeport" },
+ { /* sentinel */ },
+};
+MODULE_DEVICE_TABLE(of, rb_poeport_of_match);
+
+static struct platform_driver rb_poeport_driver = {
+ .probe = rb_poeport_probe,
+ .driver = {
+ .name = "rb-poeport-driver",
+ .of_match_table = of_match_ptr(rb_poeport_of_match),
+ },
+};
+
+module_platform_driver(rb_poeport_driver);
+
+MODULE_AUTHOR("Oskari Lemmela <oskari at lemmela.net>");
+MODULE_DESCRIPTION("Mikrotik poeport driver");
+MODULE_LICENSE("GPL");
diff --git a/target/linux/ath79/mikrotik/config-default b/target/linux/ath79/mikrotik/config-default
index 175c4fac1f..8348998421 100644
--- a/target/linux/ath79/mikrotik/config-default
+++ b/target/linux/ath79/mikrotik/config-default
@@ -7,6 +7,7 @@ CONFIG_GPIO_RB91X_KEY=y
CONFIG_GPIO_RB4XX=y
CONFIG_GPIO_WATCHDOG=y
CONFIG_GPIO_WATCHDOG_ARCH_INITCALL=y
+CONFIG_HWMON=y
CONFIG_LEDS_RESET=y
CONFIG_LZO_DECOMPRESS=y
CONFIG_MDIO_GPIO=y
@@ -36,6 +37,8 @@ CONFIG_PCI_AR71XX=y
CONFIG_PHY_AR7100_USB=y
CONFIG_PHY_AR7200_USB=y
CONFIG_REGULATOR_FIXED_VOLTAGE=y
+CONFIG_SENSORS_MIKROTIK_POE=y
+CONFIG_SENSORS_MIKROTIK_POEPORT=y
CONFIG_SPI_RB4XX=y
CONFIG_UBIFS_FS=y
CONFIG_WATCHDOG_CORE=y
diff --git a/target/linux/ath79/patches-5.10/902-hwmon-support-for-mikrotik-poe.patch b/target/linux/ath79/patches-5.10/902-hwmon-support-for-mikrotik-poe.patch
new file mode 100644
index 0000000000..ac33e2061c
--- /dev/null
+++ b/target/linux/ath79/patches-5.10/902-hwmon-support-for-mikrotik-poe.patch
@@ -0,0 +1,51 @@
+From beb4c6b6e4c186ffaec860a7c5f83ff582c37413 Mon Sep 17 00:00:00 2001
+From: Oskari Lemmela <oskari at lemmela.net>
+Date: Fri, 3 Dec 2021 15:28:49 +0200
+Subject: [PATCH] hwmon: support for mikrotik poe
+
+Signed-off-by: Oskari Lemmela <oskari at lemmela.net>
+---
+ drivers/hwmon/Kconfig | 13 +++++++++++++
+ drivers/hwmon/Makefile | 2 ++
+ 2 files changed, 15 insertions(+)
+
+diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
+index c4578e8f34bb..9bcd1df93565 100644
+--- a/drivers/hwmon/Kconfig
++++ b/drivers/hwmon/Kconfig
+@@ -1541,6 +1541,19 @@ config SENSORS_RASPBERRYPI_HWMON
+ This driver can also be built as a module. If so, the module
+ will be called raspberrypi-hwmon.
+
++config SENSORS_MIKROTIK_POE
++ tristate "Mikrotik POE driver"
++ select CRC8
++ depends on SPI
++ help
++ If you say yes here you get support for Mikrotik POE
++
++config SENSORS_MIKROTIK_POEPORT
++ tristate "Mikrotik POE port driver"
++ depends on SENSORS_MIKROTIK_POE
++ help
++ If you say yes here you get support for Mikrotik POE
++
+ config SENSORS_SL28CPLD
+ tristate "Kontron sl28cpld hardware monitoring driver"
+ depends on MFD_SL28CPLD || COMPILE_TEST
+diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
+index 162940270661..2778b2562ee1 100644
+--- a/drivers/hwmon/Makefile
++++ b/drivers/hwmon/Makefile
+@@ -162,6 +162,8 @@ obj-$(CONFIG_SENSORS_PCF8591) += pcf8591.o
+ obj-$(CONFIG_SENSORS_POWR1220) += powr1220.o
+ obj-$(CONFIG_SENSORS_PWM_FAN) += pwm-fan.o
+ obj-$(CONFIG_SENSORS_RASPBERRYPI_HWMON) += raspberrypi-hwmon.o
++obj-$(CONFIG_SENSORS_MIKROTIK_POE) += rbpoe.o
++obj-$(CONFIG_SENSORS_MIKROTIK_POEPORT) += rbpoeport.o
+ obj-$(CONFIG_SENSORS_S3C) += s3c-hwmon.o
+ obj-$(CONFIG_SENSORS_SBTSI) += sbtsi_temp.o
+ obj-$(CONFIG_SENSORS_SBRMI) += sbrmi.o
+--
+2.25.1
+
--
2.25.1
More information about the openwrt-devel
mailing list