diff --git a/Makefile b/Makefile index 627f4908c..86cf03519 100644 --- a/Makefile +++ b/Makefile @@ -41,12 +41,12 @@ $(addprefix $(DEST)/, $(MAIN_TARGET)): $(DEST)/% : fakeroot make -f debian/rules.gen setup_amd64_none_amd64 # Applying patches and configuration changes - # git init - # git add -f * - # git add debian/build/build_amd64_none_amd64/.config -f - # git commit -m "unmodified debian source" - # stg init - # stg import -s ../patch/series + git init + git add -f * + git add debian/build/build_amd64_none_amd64/.config -f + git commit -m "unmodified debian source" + stg init + stg import -s ../patch/series # Building a custom kernel from Debian kernel source DO_DOCS=False fakeroot make -f debian/rules -j $(shell nproc) binary-indep diff --git a/patch/0001-i2c-mlxcpld-add-master-driver-for-Mellanox-systems.patch b/patch/0001-i2c-mlxcpld-add-master-driver-for-Mellanox-systems.patch new file mode 100644 index 000000000..54431bcc5 --- /dev/null +++ b/patch/0001-i2c-mlxcpld-add-master-driver-for-Mellanox-systems.patch @@ -0,0 +1,544 @@ +Linux backport patch. Includes following commits: +899e11216e1c215b97f2f8f92c7b010a4e88f38e + + +diff -Nur a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig +--- a/drivers/i2c/busses/Kconfig 2017-11-12 08:08:32.136039784 +0000 ++++ b/drivers/i2c/busses/Kconfig 2017-11-12 08:08:40.776039899 +0000 +@@ -1150,6 +1150,17 @@ + This support is also available as a module. If so, the module + will be called i2c-elektor. + ++config I2C_MLXCPLD ++ tristate "Mellanox I2C driver" ++ depends on X86_64 ++ help ++ This exposes the Mellanox platform I2C busses to the linux I2C layer ++ for X86 based systems. ++ Controller is implemented as CPLD logic. ++ ++ This driver can also be built as a module. If so, the module will be ++ called as i2c-mlxcpld. ++ + config I2C_PCA_ISA + tristate "PCA9564/PCA9665 on an ISA bus" + depends on ISA +diff -Nur a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile +--- a/drivers/i2c/busses/Makefile 2017-11-12 08:08:32.140039784 +0000 ++++ b/drivers/i2c/busses/Makefile 2017-11-12 08:08:40.780039899 +0000 +@@ -116,6 +116,7 @@ + obj-$(CONFIG_I2C_BRCMSTB) += i2c-brcmstb.o + obj-$(CONFIG_I2C_CROS_EC_TUNNEL) += i2c-cros-ec-tunnel.o + obj-$(CONFIG_I2C_ELEKTOR) += i2c-elektor.o ++obj-$(CONFIG_I2C_MLXCPLD) += i2c-mlxcpld.o + obj-$(CONFIG_I2C_OPAL) += i2c-opal.o + obj-$(CONFIG_I2C_PCA_ISA) += i2c-pca-isa.o + obj-$(CONFIG_I2C_SIBYTE) += i2c-sibyte.o +diff -Nur a/drivers/i2c/busses/i2c-mlxcpld.c b/drivers/i2c/busses/i2c-mlxcpld.c +--- a/drivers/i2c/busses/i2c-mlxcpld.c 1970-01-01 00:00:00.000000000 +0000 ++++ b/drivers/i2c/busses/i2c-mlxcpld.c 2017-11-12 08:08:40.780039899 +0000 +@@ -0,0 +1,504 @@ ++/* ++ * Copyright (c) 2016 Mellanox Technologies. All rights reserved. ++ * Copyright (c) 2016 Michael Shych ++ * ++ * Redistribution and use in source and binary forms, with or without ++ * modification, are permitted provided that the following conditions are met: ++ * ++ * 1. Redistributions of source code must retain the above copyright ++ * notice, this list of conditions and the following disclaimer. ++ * 2. Redistributions in binary form must reproduce the above copyright ++ * notice, this list of conditions and the following disclaimer in the ++ * documentation and/or other materials provided with the distribution. ++ * 3. Neither the names of the copyright holders nor the names of its ++ * contributors may be used to endorse or promote products derived from ++ * this software without specific prior written permission. ++ * ++ * Alternatively, this software may be distributed under the terms of the ++ * GNU General Public License ("GPL") version 2 as published by the Free ++ * Software Foundation. ++ * ++ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" ++ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE ++ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ++ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE ++ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR ++ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF ++ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS ++ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN ++ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ++ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE ++ * POSSIBILITY OF SUCH DAMAGE. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++/* General defines */ ++#define MLXPLAT_CPLD_LPC_I2C_BASE_ADDR 0x2000 ++#define MLXCPLD_I2C_DEVICE_NAME "i2c_mlxcpld" ++#define MLXCPLD_I2C_VALID_FLAG (I2C_M_RECV_LEN | I2C_M_RD) ++#define MLXCPLD_I2C_BUS_NUM 1 ++#define MLXCPLD_I2C_DATA_REG_SZ 36 ++#define MLXCPLD_I2C_MAX_ADDR_LEN 4 ++#define MLXCPLD_I2C_RETR_NUM 2 ++#define MLXCPLD_I2C_XFER_TO 500000 /* usec */ ++#define MLXCPLD_I2C_POLL_TIME 2000 /* usec */ ++ ++/* LPC I2C registers */ ++#define MLXCPLD_LPCI2C_LPF_REG 0x0 ++#define MLXCPLD_LPCI2C_CTRL_REG 0x1 ++#define MLXCPLD_LPCI2C_HALF_CYC_REG 0x4 ++#define MLXCPLD_LPCI2C_I2C_HOLD_REG 0x5 ++#define MLXCPLD_LPCI2C_CMD_REG 0x6 ++#define MLXCPLD_LPCI2C_NUM_DAT_REG 0x7 ++#define MLXCPLD_LPCI2C_NUM_ADDR_REG 0x8 ++#define MLXCPLD_LPCI2C_STATUS_REG 0x9 ++#define MLXCPLD_LPCI2C_DATA_REG 0xa ++ ++/* LPC I2C masks and parametres */ ++#define MLXCPLD_LPCI2C_RST_SEL_MASK 0x1 ++#define MLXCPLD_LPCI2C_TRANS_END 0x1 ++#define MLXCPLD_LPCI2C_STATUS_NACK 0x10 ++#define MLXCPLD_LPCI2C_NO_IND 0 ++#define MLXCPLD_LPCI2C_ACK_IND 1 ++#define MLXCPLD_LPCI2C_NACK_IND 2 ++ ++struct mlxcpld_i2c_curr_xfer { ++ u8 cmd; ++ u8 addr_width; ++ u8 data_len; ++ u8 msg_num; ++ struct i2c_msg *msg; ++}; ++ ++struct mlxcpld_i2c_priv { ++ struct i2c_adapter adap; ++ u32 base_addr; ++ struct mutex lock; ++ struct mlxcpld_i2c_curr_xfer xfer; ++ struct device *dev; ++}; ++ ++static void mlxcpld_i2c_lpc_write_buf(u8 *data, u8 len, u32 addr) ++{ ++ int i; ++ ++ for (i = 0; i < len - len % 4; i += 4) ++ outl(*(u32 *)(data + i), addr + i); ++ for (; i < len; ++i) ++ outb(*(data + i), addr + i); ++} ++ ++static void mlxcpld_i2c_lpc_read_buf(u8 *data, u8 len, u32 addr) ++{ ++ int i; ++ ++ for (i = 0; i < len - len % 4; i += 4) ++ *(u32 *)(data + i) = inl(addr + i); ++ for (; i < len; ++i) ++ *(data + i) = inb(addr + i); ++} ++ ++static void mlxcpld_i2c_read_comm(struct mlxcpld_i2c_priv *priv, u8 offs, ++ u8 *data, u8 datalen) ++{ ++ u32 addr = priv->base_addr + offs; ++ ++ switch (datalen) { ++ case 1: ++ *(data) = inb(addr); ++ break; ++ case 2: ++ *((u16 *)data) = inw(addr); ++ break; ++ case 3: ++ *((u16 *)data) = inw(addr); ++ *(data + 2) = inb(addr + 2); ++ break; ++ case 4: ++ *((u32 *)data) = inl(addr); ++ break; ++ default: ++ mlxcpld_i2c_lpc_read_buf(data, datalen, addr); ++ break; ++ } ++} ++ ++static void mlxcpld_i2c_write_comm(struct mlxcpld_i2c_priv *priv, u8 offs, ++ u8 *data, u8 datalen) ++{ ++ u32 addr = priv->base_addr + offs; ++ ++ switch (datalen) { ++ case 1: ++ outb(*(data), addr); ++ break; ++ case 2: ++ outw(*((u16 *)data), addr); ++ break; ++ case 3: ++ outw(*((u16 *)data), addr); ++ outb(*(data + 2), addr + 2); ++ break; ++ case 4: ++ outl(*((u32 *)data), addr); ++ break; ++ default: ++ mlxcpld_i2c_lpc_write_buf(data, datalen, addr); ++ break; ++ } ++} ++ ++/* ++ * Check validity of received i2c messages parameters. ++ * Returns 0 if OK, other - in case of invalid parameters. ++ */ ++static int mlxcpld_i2c_check_msg_params(struct mlxcpld_i2c_priv *priv, ++ struct i2c_msg *msgs, int num) ++{ ++ int i; ++ ++ if (!num) { ++ dev_err(priv->dev, "Incorrect 0 num of messages\n"); ++ return -EINVAL; ++ } ++ ++ if (unlikely(msgs[0].addr > 0x7f)) { ++ dev_err(priv->dev, "Invalid address 0x%03x\n", ++ msgs[0].addr); ++ return -EINVAL; ++ } ++ ++ for (i = 0; i < num; ++i) { ++ if (unlikely(!msgs[i].buf)) { ++ dev_err(priv->dev, "Invalid buf in msg[%d]\n", ++ i); ++ return -EINVAL; ++ } ++ if (unlikely(msgs[0].addr != msgs[i].addr)) { ++ dev_err(priv->dev, "Invalid addr in msg[%d]\n", ++ i); ++ return -EINVAL; ++ } ++ } ++ ++ return 0; ++} ++ ++/* ++ * Check if transfer is completed and status of operation. ++ * Returns 0 - transfer completed (both ACK or NACK), ++ * negative - transfer isn't finished. ++ */ ++static int mlxcpld_i2c_check_status(struct mlxcpld_i2c_priv *priv, int *status) ++{ ++ u8 val; ++ ++ mlxcpld_i2c_read_comm(priv, MLXCPLD_LPCI2C_STATUS_REG, &val, 1); ++ ++ if (val & MLXCPLD_LPCI2C_TRANS_END) { ++ if (val & MLXCPLD_LPCI2C_STATUS_NACK) ++ /* ++ * The slave is unable to accept the data. No such ++ * slave, command not understood, or unable to accept ++ * any more data. ++ */ ++ *status = MLXCPLD_LPCI2C_NACK_IND; ++ else ++ *status = MLXCPLD_LPCI2C_ACK_IND; ++ return 0; ++ } ++ *status = MLXCPLD_LPCI2C_NO_IND; ++ ++ return -EIO; ++} ++ ++static void mlxcpld_i2c_set_transf_data(struct mlxcpld_i2c_priv *priv, ++ struct i2c_msg *msgs, int num, ++ u8 comm_len) ++{ ++ priv->xfer.msg = msgs; ++ priv->xfer.msg_num = num; ++ ++ /* ++ * All upper layers currently are never use transfer with more than ++ * 2 messages. Actually, it's also not so relevant in Mellanox systems ++ * because of HW limitation. Max size of transfer is not more than 32 ++ * bytes in the current x86 LPCI2C bridge. ++ */ ++ priv->xfer.cmd = msgs[num - 1].flags & I2C_M_RD; ++ ++ if (priv->xfer.cmd == I2C_M_RD && comm_len != msgs[0].len) { ++ priv->xfer.addr_width = msgs[0].len; ++ priv->xfer.data_len = comm_len - priv->xfer.addr_width; ++ } else { ++ priv->xfer.addr_width = 0; ++ priv->xfer.data_len = comm_len; ++ } ++} ++ ++/* Reset CPLD LPCI2C block */ ++static void mlxcpld_i2c_reset(struct mlxcpld_i2c_priv *priv) ++{ ++ u8 val; ++ ++ mutex_lock(&priv->lock); ++ ++ mlxcpld_i2c_read_comm(priv, MLXCPLD_LPCI2C_CTRL_REG, &val, 1); ++ val &= ~MLXCPLD_LPCI2C_RST_SEL_MASK; ++ mlxcpld_i2c_write_comm(priv, MLXCPLD_LPCI2C_CTRL_REG, &val, 1); ++ ++ mutex_unlock(&priv->lock); ++} ++ ++/* Make sure the CPLD is ready to start transmitting. */ ++static int mlxcpld_i2c_check_busy(struct mlxcpld_i2c_priv *priv) ++{ ++ u8 val; ++ ++ mlxcpld_i2c_read_comm(priv, MLXCPLD_LPCI2C_STATUS_REG, &val, 1); ++ ++ if (val & MLXCPLD_LPCI2C_TRANS_END) ++ return 0; ++ ++ return -EIO; ++} ++ ++static int mlxcpld_i2c_wait_for_free(struct mlxcpld_i2c_priv *priv) ++{ ++ int timeout = 0; ++ ++ do { ++ if (!mlxcpld_i2c_check_busy(priv)) ++ break; ++ usleep_range(MLXCPLD_I2C_POLL_TIME / 2, MLXCPLD_I2C_POLL_TIME); ++ timeout += MLXCPLD_I2C_POLL_TIME; ++ } while (timeout <= MLXCPLD_I2C_XFER_TO); ++ ++ if (timeout > MLXCPLD_I2C_XFER_TO) ++ return -ETIMEDOUT; ++ ++ return 0; ++} ++ ++/* ++ * Wait for master transfer to complete. ++ * It puts current process to sleep until we get interrupt or timeout expires. ++ * Returns the number of transferred or read bytes or error (<0). ++ */ ++static int mlxcpld_i2c_wait_for_tc(struct mlxcpld_i2c_priv *priv) ++{ ++ int status, i, timeout = 0; ++ u8 datalen; ++ ++ do { ++ usleep_range(MLXCPLD_I2C_POLL_TIME / 2, MLXCPLD_I2C_POLL_TIME); ++ if (!mlxcpld_i2c_check_status(priv, &status)) ++ break; ++ timeout += MLXCPLD_I2C_POLL_TIME; ++ } while (status == 0 && timeout < MLXCPLD_I2C_XFER_TO); ++ ++ switch (status) { ++ case MLXCPLD_LPCI2C_NO_IND: ++ return -ETIMEDOUT; ++ ++ case MLXCPLD_LPCI2C_ACK_IND: ++ if (priv->xfer.cmd != I2C_M_RD) ++ return (priv->xfer.addr_width + priv->xfer.data_len); ++ ++ if (priv->xfer.msg_num == 1) ++ i = 0; ++ else ++ i = 1; ++ ++ if (!priv->xfer.msg[i].buf) ++ return -EINVAL; ++ ++ /* ++ * Actual read data len will be always the same as ++ * requested len. 0xff (line pull-up) will be returned ++ * if slave has no data to return. Thus don't read ++ * MLXCPLD_LPCI2C_NUM_DAT_REG reg from CPLD. ++ */ ++ datalen = priv->xfer.data_len; ++ ++ mlxcpld_i2c_read_comm(priv, MLXCPLD_LPCI2C_DATA_REG, ++ priv->xfer.msg[i].buf, datalen); ++ ++ return datalen; ++ ++ case MLXCPLD_LPCI2C_NACK_IND: ++ return -ENXIO; ++ ++ default: ++ return -EINVAL; ++ } ++} ++ ++static void mlxcpld_i2c_xfer_msg(struct mlxcpld_i2c_priv *priv) ++{ ++ int i, len = 0; ++ u8 cmd; ++ ++ mlxcpld_i2c_write_comm(priv, MLXCPLD_LPCI2C_NUM_DAT_REG, ++ &priv->xfer.data_len, 1); ++ mlxcpld_i2c_write_comm(priv, MLXCPLD_LPCI2C_NUM_ADDR_REG, ++ &priv->xfer.addr_width, 1); ++ ++ for (i = 0; i < priv->xfer.msg_num; i++) { ++ if ((priv->xfer.msg[i].flags & I2C_M_RD) != I2C_M_RD) { ++ /* Don't write to CPLD buffer in read transaction */ ++ mlxcpld_i2c_write_comm(priv, MLXCPLD_LPCI2C_DATA_REG + ++ len, priv->xfer.msg[i].buf, ++ priv->xfer.msg[i].len); ++ len += priv->xfer.msg[i].len; ++ } ++ } ++ ++ /* ++ * Set target slave address with command for master transfer. ++ * It should be latest executed function before CPLD transaction. ++ */ ++ cmd = (priv->xfer.msg[0].addr << 1) | priv->xfer.cmd; ++ mlxcpld_i2c_write_comm(priv, MLXCPLD_LPCI2C_CMD_REG, &cmd, 1); ++} ++ ++/* ++ * Generic lpc-i2c transfer. ++ * Returns the number of processed messages or error (<0). ++ */ ++static int mlxcpld_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs, ++ int num) ++{ ++ struct mlxcpld_i2c_priv *priv = i2c_get_adapdata(adap); ++ u8 comm_len = 0; ++ int i, err; ++ ++ err = mlxcpld_i2c_check_msg_params(priv, msgs, num); ++ if (err) { ++ dev_err(priv->dev, "Incorrect message\n"); ++ return err; ++ } ++ ++ for (i = 0; i < num; ++i) ++ comm_len += msgs[i].len; ++ ++ /* Check bus state */ ++ if (mlxcpld_i2c_wait_for_free(priv)) { ++ dev_err(priv->dev, "LPCI2C bridge is busy\n"); ++ ++ /* ++ * Usually it means something serious has happened. ++ * We can not have unfinished previous transfer ++ * so it doesn't make any sense to try to stop it. ++ * Probably we were not able to recover from the ++ * previous error. ++ * The only reasonable thing - is soft reset. ++ */ ++ mlxcpld_i2c_reset(priv); ++ if (mlxcpld_i2c_check_busy(priv)) { ++ dev_err(priv->dev, "LPCI2C bridge is busy after reset\n"); ++ return -EIO; ++ } ++ } ++ ++ mlxcpld_i2c_set_transf_data(priv, msgs, num, comm_len); ++ ++ mutex_lock(&priv->lock); ++ ++ /* Do real transfer. Can't fail */ ++ mlxcpld_i2c_xfer_msg(priv); ++ ++ /* Wait for transaction complete */ ++ err = mlxcpld_i2c_wait_for_tc(priv); ++ ++ mutex_unlock(&priv->lock); ++ ++ return err < 0 ? err : num; ++} ++ ++static u32 mlxcpld_i2c_func(struct i2c_adapter *adap) ++{ ++ return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL | I2C_FUNC_SMBUS_BLOCK_DATA; ++} ++ ++static const struct i2c_algorithm mlxcpld_i2c_algo = { ++ .master_xfer = mlxcpld_i2c_xfer, ++ .functionality = mlxcpld_i2c_func ++}; ++ ++static struct i2c_adapter_quirks mlxcpld_i2c_quirks = { ++ .flags = I2C_AQ_COMB_WRITE_THEN_READ, ++ .max_read_len = MLXCPLD_I2C_DATA_REG_SZ - MLXCPLD_I2C_MAX_ADDR_LEN, ++ .max_write_len = MLXCPLD_I2C_DATA_REG_SZ, ++ .max_comb_1st_msg_len = 4, ++}; ++ ++static struct i2c_adapter mlxcpld_i2c_adapter = { ++ .owner = THIS_MODULE, ++ .name = "i2c-mlxcpld", ++ .class = I2C_CLASS_HWMON | I2C_CLASS_SPD, ++ .algo = &mlxcpld_i2c_algo, ++ .quirks = &mlxcpld_i2c_quirks, ++ .retries = MLXCPLD_I2C_RETR_NUM, ++ .nr = MLXCPLD_I2C_BUS_NUM, ++}; ++ ++static int mlxcpld_i2c_probe(struct platform_device *pdev) ++{ ++ struct mlxcpld_i2c_priv *priv; ++ int err; ++ ++ priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); ++ if (!priv) ++ return -ENOMEM; ++ ++ mutex_init(&priv->lock); ++ platform_set_drvdata(pdev, priv); ++ ++ priv->dev = &pdev->dev; ++ ++ /* Register with i2c layer */ ++ mlxcpld_i2c_adapter.timeout = usecs_to_jiffies(MLXCPLD_I2C_XFER_TO); ++ priv->adap = mlxcpld_i2c_adapter; ++ priv->adap.dev.parent = &pdev->dev; ++ priv->base_addr = MLXPLAT_CPLD_LPC_I2C_BASE_ADDR; ++ i2c_set_adapdata(&priv->adap, priv); ++ ++ err = i2c_add_numbered_adapter(&priv->adap); ++ if (err) ++ mutex_destroy(&priv->lock); ++ ++ return err; ++} ++ ++static int mlxcpld_i2c_remove(struct platform_device *pdev) ++{ ++ struct mlxcpld_i2c_priv *priv = platform_get_drvdata(pdev); ++ ++ i2c_del_adapter(&priv->adap); ++ mutex_destroy(&priv->lock); ++ ++ return 0; ++} ++ ++static struct platform_driver mlxcpld_i2c_driver = { ++ .probe = mlxcpld_i2c_probe, ++ .remove = mlxcpld_i2c_remove, ++ .driver = { ++ .name = MLXCPLD_I2C_DEVICE_NAME, ++ }, ++}; ++ ++module_platform_driver(mlxcpld_i2c_driver); ++ ++MODULE_AUTHOR("Michael Shych "); ++MODULE_DESCRIPTION("Mellanox I2C-CPLD controller driver"); ++MODULE_LICENSE("Dual BSD/GPL"); ++MODULE_ALIAS("platform:i2c-mlxcpld"); diff --git a/patch/0002-i2c-mux-mlxcpld-add-driver-for-Mellanox-systems.patch b/patch/0002-i2c-mux-mlxcpld-add-driver-for-Mellanox-systems.patch new file mode 100644 index 000000000..4ce713858 --- /dev/null +++ b/patch/0002-i2c-mux-mlxcpld-add-driver-for-Mellanox-systems.patch @@ -0,0 +1,317 @@ +Linux backport patch. Includes following commits: +e3448e71adb1fdd7f403c568ef5c2ed5adf2b197 +c3bb77620da428884807fb2f6f3485644e146f84 +db5f807ee3dcc779b78f59982cc3e89863069e9c + + +diff -Nur a/drivers/i2c/muxes/Kconfig b/drivers/i2c/muxes/Kconfig +--- a/drivers/i2c/muxes/Kconfig 2017-11-12 08:13:59.176044126 +0000 ++++ b/drivers/i2c/muxes/Kconfig 2017-11-12 08:14:27.992044509 +0000 +@@ -82,4 +82,15 @@ + demultiplexer that uses the pinctrl subsystem. This is useful if you + want to change the I2C master at run-time depending on features. + ++config I2C_MUX_MLXCPLD ++ tristate "Mellanox CPLD based I2C multiplexer" ++ help ++ If you say yes to this option, support will be included for a ++ CPLD based I2C multiplexer. This driver provides access to ++ I2C busses connected through a MUX, which is controlled ++ by a CPLD register. ++ ++ This driver can also be built as a module. If so, the module ++ will be called i2c-mux-mlxcpld. ++ + endmenu +diff -Nur a/drivers/i2c/muxes/Makefile b/drivers/i2c/muxes/Makefile +--- a/drivers/i2c/muxes/Makefile 2017-11-12 08:13:59.176044126 +0000 ++++ b/drivers/i2c/muxes/Makefile 2017-11-12 08:14:27.992044509 +0000 +@@ -6,6 +6,7 @@ + obj-$(CONFIG_I2C_DEMUX_PINCTRL) += i2c-demux-pinctrl.o + + obj-$(CONFIG_I2C_MUX_GPIO) += i2c-mux-gpio.o ++obj-$(CONFIG_I2C_MUX_MLXCPLD) += i2c-mux-mlxcpld.o + obj-$(CONFIG_I2C_MUX_PCA9541) += i2c-mux-pca9541.o + obj-$(CONFIG_I2C_MUX_PCA954x) += i2c-mux-pca954x.o + obj-$(CONFIG_I2C_MUX_PINCTRL) += i2c-mux-pinctrl.o +diff -Nur a/drivers/i2c/muxes/i2c-mux-mlxcpld.c b/drivers/i2c/muxes/i2c-mux-mlxcpld.c +--- a/drivers/i2c/muxes/i2c-mux-mlxcpld.c 1970-01-01 00:00:00.000000000 +0000 ++++ b/drivers/i2c/muxes/i2c-mux-mlxcpld.c 2017-11-12 08:14:27.992044509 +0000 +@@ -0,0 +1,221 @@ ++/* ++ * drivers/i2c/muxes/i2c-mux-mlxcpld.c ++ * Copyright (c) 2016 Mellanox Technologies. All rights reserved. ++ * Copyright (c) 2016 Michael Shych ++ * ++ * Redistribution and use in source and binary forms, with or without ++ * modification, are permitted provided that the following conditions are met: ++ * ++ * 1. Redistributions of source code must retain the above copyright ++ * notice, this list of conditions and the following disclaimer. ++ * 2. Redistributions in binary form must reproduce the above copyright ++ * notice, this list of conditions and the following disclaimer in the ++ * documentation and/or other materials provided with the distribution. ++ * 3. Neither the names of the copyright holders nor the names of its ++ * contributors may be used to endorse or promote products derived from ++ * this software without specific prior written permission. ++ * ++ * Alternatively, this software may be distributed under the terms of the ++ * GNU General Public License ("GPL") version 2 as published by the Free ++ * Software Foundation. ++ * ++ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" ++ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE ++ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ++ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE ++ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR ++ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF ++ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS ++ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN ++ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ++ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE ++ * POSSIBILITY OF SUCH DAMAGE. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#define CPLD_MUX_MAX_NCHANS 8 ++ ++/* mlxcpld_mux - mux control structure: ++ * @last_chan - last register value ++ * @client - I2C device client ++ */ ++struct mlxcpld_mux { ++ u8 last_chan; ++ struct i2c_client *client; ++}; ++ ++/* MUX logic description. ++ * Driver can support different mux control logic, according to CPLD ++ * implementation. ++ * ++ * Connectivity schema. ++ * ++ * i2c-mlxcpld Digital Analog ++ * driver ++ * *--------* * -> mux1 (virt bus2) -> mux -> | ++ * | I2CLPC | i2c physical * -> mux2 (virt bus3) -> mux -> | ++ * | bridge | bus 1 *---------* | ++ * | logic |---------------------> * mux reg * | ++ * | in CPLD| *---------* | ++ * *--------* i2c-mux-mlxpcld ^ * -> muxn (virt busn) -> mux -> | ++ * | driver | | ++ * | *---------------* | Devices ++ * | * CPLD (i2c bus)* select | ++ * | * registers for *--------* ++ * | * mux selection * deselect ++ * | *---------------* ++ * | | ++ * <--------> <-----------> ++ * i2c cntrl Board cntrl reg ++ * reg space space (mux select, ++ * IO, LED, WD, info) ++ * ++ */ ++ ++static const struct i2c_device_id mlxcpld_mux_id[] = { ++ { "mlxcpld_mux_module", 0 }, ++ { } ++}; ++MODULE_DEVICE_TABLE(i2c, mlxcpld_mux_id); ++ ++/* Write to mux register. Don't use i2c_transfer() and i2c_smbus_xfer() ++ * for this as they will try to lock adapter a second time. ++ */ ++static int mlxcpld_mux_reg_write(struct i2c_adapter *adap, ++ struct i2c_client *client, u8 val) ++{ ++ struct mlxcpld_mux_plat_data *pdata = dev_get_platdata(&client->dev); ++ int ret = -ENODEV; ++ ++ if (adap->algo->master_xfer) { ++ struct i2c_msg msg; ++ u8 msgbuf[] = {pdata->sel_reg_addr, val}; ++ ++ msg.addr = client->addr; ++ msg.flags = 0; ++ msg.len = 2; ++ msg.buf = msgbuf; ++ ret = __i2c_transfer(adap, &msg, 1); ++ ++ if (ret >= 0 && ret != 1) ++ ret = -EREMOTEIO; ++ } else if (adap->algo->smbus_xfer) { ++ union i2c_smbus_data data; ++ ++ data.byte = val; ++ ret = adap->algo->smbus_xfer(adap, client->addr, ++ client->flags, I2C_SMBUS_WRITE, ++ pdata->sel_reg_addr, ++ I2C_SMBUS_BYTE_DATA, &data); ++ } ++ ++ return ret; ++} ++ ++static int mlxcpld_mux_select_chan(struct i2c_mux_core *muxc, u32 chan) ++{ ++ struct mlxcpld_mux *data = i2c_mux_priv(muxc); ++ struct i2c_client *client = data->client; ++ u8 regval = chan + 1; ++ int err = 0; ++ ++ /* Only select the channel if its different from the last channel */ ++ if (data->last_chan != regval) { ++ err = mlxcpld_mux_reg_write(muxc->parent, client, regval); ++ data->last_chan = err < 0 ? 0 : regval; ++ } ++ ++ return err; ++} ++ ++static int mlxcpld_mux_deselect(struct i2c_mux_core *muxc, u32 chan) ++{ ++ struct mlxcpld_mux *data = i2c_mux_priv(muxc); ++ struct i2c_client *client = data->client; ++ ++ /* Deselect active channel */ ++ data->last_chan = 0; ++ ++ return mlxcpld_mux_reg_write(muxc->parent, client, data->last_chan); ++} ++ ++/* Probe/reomove functions */ ++static int mlxcpld_mux_probe(struct i2c_client *client, ++ const struct i2c_device_id *id) ++{ ++ struct i2c_adapter *adap = to_i2c_adapter(client->dev.parent); ++ struct mlxcpld_mux_plat_data *pdata = dev_get_platdata(&client->dev); ++ struct i2c_mux_core *muxc; ++ int num, force; ++ struct mlxcpld_mux *data; ++ int err; ++ ++ if (!pdata) ++ return -EINVAL; ++ ++ if (!i2c_check_functionality(adap, I2C_FUNC_SMBUS_WRITE_BYTE_DATA)) ++ return -ENODEV; ++ ++ muxc = i2c_mux_alloc(adap, &client->dev, CPLD_MUX_MAX_NCHANS, ++ sizeof(*data), 0, mlxcpld_mux_select_chan, ++ mlxcpld_mux_deselect); ++ if (!muxc) ++ return -ENOMEM; ++ ++ data = i2c_mux_priv(muxc); ++ i2c_set_clientdata(client, muxc); ++ data->client = client; ++ data->last_chan = 0; /* force the first selection */ ++ ++ /* Create an adapter for each channel. */ ++ for (num = 0; num < CPLD_MUX_MAX_NCHANS; num++) { ++ if (num >= pdata->num_adaps) ++ /* discard unconfigured channels */ ++ break; ++ ++ force = pdata->adap_ids[num]; ++ ++ err = i2c_mux_add_adapter(muxc, force, num, 0); ++ if (err) ++ goto virt_reg_failed; ++ } ++ ++ return 0; ++ ++virt_reg_failed: ++ i2c_mux_del_adapters(muxc); ++ return err; ++} ++ ++static int mlxcpld_mux_remove(struct i2c_client *client) ++{ ++ struct i2c_mux_core *muxc = i2c_get_clientdata(client); ++ ++ i2c_mux_del_adapters(muxc); ++ return 0; ++} ++ ++static struct i2c_driver mlxcpld_mux_driver = { ++ .driver = { ++ .name = "mlxcpld-mux", ++ }, ++ .probe = mlxcpld_mux_probe, ++ .remove = mlxcpld_mux_remove, ++ .id_table = mlxcpld_mux_id, ++}; ++ ++module_i2c_driver(mlxcpld_mux_driver); ++ ++MODULE_AUTHOR("Michael Shych (michaels@mellanox.com)"); ++MODULE_DESCRIPTION("Mellanox I2C-CPLD-MUX driver"); ++MODULE_LICENSE("Dual BSD/GPL"); ++MODULE_ALIAS("platform:i2c-mux-mlxcpld"); +diff -Nur a/include/linux/i2c/mlxcpld.h b/include/linux/i2c/mlxcpld.h +--- a/include/linux/i2c/mlxcpld.h 1970-01-01 00:00:00.000000000 +0000 ++++ b/include/linux/i2c/mlxcpld.h 2017-11-12 08:17:03.032046568 +0000 +@@ -0,0 +1,52 @@ ++/* ++ * mlxcpld.h - Mellanox I2C multiplexer support in CPLD ++ * ++ * Copyright (c) 2016 Mellanox Technologies. All rights reserved. ++ * Copyright (c) 2016 Michael Shych ++ * ++ * Redistribution and use in source and binary forms, with or without ++ * modification, are permitted provided that the following conditions are met: ++ * ++ * 1. Redistributions of source code must retain the above copyright ++ * notice, this list of conditions and the following disclaimer. ++ * 2. Redistributions in binary form must reproduce the above copyright ++ * notice, this list of conditions and the following disclaimer in the ++ * documentation and/or other materials provided with the distribution. ++ * 3. Neither the names of the copyright holders nor the names of its ++ * contributors may be used to endorse or promote products derived from ++ * this software without specific prior written permission. ++ * ++ * Alternatively, this software may be distributed under the terms of the ++ * GNU General Public License ("GPL") version 2 as published by the Free ++ * Software Foundation. ++ * ++ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" ++ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE ++ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ++ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE ++ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR ++ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF ++ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS ++ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN ++ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ++ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE ++ * POSSIBILITY OF SUCH DAMAGE. ++ */ ++ ++#ifndef _LINUX_I2C_MLXCPLD_H ++#define _LINUX_I2C_MLXCPLD_H ++ ++/* Platform data for the CPLD I2C multiplexers */ ++ ++/* mlxcpld_mux_plat_data - per mux data, used with i2c_register_board_info ++ * @adap_ids - adapter array ++ * @num_adaps - number of adapters ++ * @sel_reg_addr - mux select register offset in CPLD space ++ */ ++struct mlxcpld_mux_plat_data { ++ int *adap_ids; ++ int num_adaps; ++ int sel_reg_addr; ++}; ++ ++#endif /* _LINUX_I2C_MLXCPLD_H */ diff --git a/patch/0003-platform-mellanox-Introduce-Mellanox-hardware-platfo.patch b/patch/0003-platform-mellanox-Introduce-Mellanox-hardware-platfo.patch new file mode 100644 index 000000000..a2b6a9f45 --- /dev/null +++ b/patch/0003-platform-mellanox-Introduce-Mellanox-hardware-platfo.patch @@ -0,0 +1,1463 @@ +Linux backport patch. Includes following commits: +2926024b5081fc8d4b086677bafa1ac55ea0b911 +eab85698a91e0191e1a00b547ebded411dd9061c +91973760712f350048a0fa8e0363e260bf874313 +ae4c6f185a791890dcf9cab7bc7764ba44e2e696 +90fcad9b077a4660896dbdc815509cee3440886b +290e505b287fe664549cb72b26507d8ffb4a6d39 +d53bc5dc941653f0ed93b11a647bd6ff40f40ef2 +daf155fe70c9d69c28bba632b6a758ac8feab6e7 +c7c1fe5b608cd11668834761404f9d03bf616bdd + + +diff -Nur a/arch/x86/Kconfig b/arch/x86/Kconfig +--- a/arch/x86/Kconfig 2017-11-12 10:26:01.808149323 +0000 ++++ b/arch/x86/Kconfig 2017-11-12 10:27:38.576150608 +0000 +@@ -550,18 +550,6 @@ + Say Y here if you have a Quark based system such as the Arduino + compatible Intel Galileo. + +-config MLX_PLATFORM +- tristate "Mellanox Technologies platform support" +- depends on X86_64 +- depends on X86_EXTENDED_PLATFORM +- ---help--- +- This option enables system support for the Mellanox Technologies +- platform. +- +- Say Y here if you are building a kernel for Mellanox system. +- +- Otherwise, say N. +- + config X86_INTEL_LPSS + bool "Intel Low Power Subsystem Support" + depends on X86 && ACPI +diff -Nur a/arch/x86/platform/Makefile b/arch/x86/platform/Makefile +--- a/arch/x86/platform/Makefile 2017-11-12 10:25:48.640149148 +0000 ++++ b/arch/x86/platform/Makefile 2017-11-12 10:27:24.924150426 +0000 +@@ -8,7 +8,6 @@ + obj-y += intel/ + obj-y += intel-mid/ + obj-y += intel-quark/ +-obj-y += mellanox/ + obj-y += olpc/ + obj-y += scx200/ + obj-y += sfi/ +diff -Nur a/arch/x86/platform/mellanox/Makefile b/arch/x86/platform/mellanox/Makefile +--- a/arch/x86/platform/mellanox/Makefile 2017-11-12 10:25:26.704148857 +0000 ++++ b/arch/x86/platform/mellanox/Makefile 1970-01-01 00:00:00.000000000 +0000 +@@ -1 +0,0 @@ +-obj-$(CONFIG_MLX_PLATFORM) += mlx-platform.o +diff -Nur a/arch/x86/platform/mellanox/mlx-platform.c b/arch/x86/platform/mellanox/mlx-platform.c +--- a/arch/x86/platform/mellanox/mlx-platform.c 2017-11-12 10:25:26.704148857 +0000 ++++ b/arch/x86/platform/mellanox/mlx-platform.c 1970-01-01 00:00:00.000000000 +0000 +@@ -1,266 +0,0 @@ +-/* +- * arch/x86/platform/mellanox/mlx-platform.c +- * Copyright (c) 2016 Mellanox Technologies. All rights reserved. +- * Copyright (c) 2016 Vadim Pasternak +- * +- * Redistribution and use in source and binary forms, with or without +- * modification, are permitted provided that the following conditions are met: +- * +- * 1. Redistributions of source code must retain the above copyright +- * notice, this list of conditions and the following disclaimer. +- * 2. Redistributions in binary form must reproduce the above copyright +- * notice, this list of conditions and the following disclaimer in the +- * documentation and/or other materials provided with the distribution. +- * 3. Neither the names of the copyright holders nor the names of its +- * contributors may be used to endorse or promote products derived from +- * this software without specific prior written permission. +- * +- * Alternatively, this software may be distributed under the terms of the +- * GNU General Public License ("GPL") version 2 as published by the Free +- * Software Foundation. +- * +- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +- * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +- * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +- * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +- * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +- * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +- * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +- * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +- * POSSIBILITY OF SUCH DAMAGE. +- */ +- +-#include +-#include +-#include +-#include +-#include +-#include +-#include +- +-#define MLX_PLAT_DEVICE_NAME "mlxplat" +- +-/* LPC bus IO offsets */ +-#define MLXPLAT_CPLD_LPC_I2C_BASE_ADRR 0x2000 +-#define MLXPLAT_CPLD_LPC_REG_BASE_ADRR 0x2500 +-#define MLXPLAT_CPLD_LPC_IO_RANGE 0x100 +-#define MLXPLAT_CPLD_LPC_I2C_CH1_OFF 0xdb +-#define MLXPLAT_CPLD_LPC_I2C_CH2_OFF 0xda +-#define MLXPLAT_CPLD_LPC_PIO_OFFSET 0x10000UL +-#define MLXPLAT_CPLD_LPC_REG1 ((MLXPLAT_CPLD_LPC_REG_BASE_ADRR + \ +- MLXPLAT_CPLD_LPC_I2C_CH1_OFF) | \ +- MLXPLAT_CPLD_LPC_PIO_OFFSET) +-#define MLXPLAT_CPLD_LPC_REG2 ((MLXPLAT_CPLD_LPC_REG_BASE_ADRR + \ +- MLXPLAT_CPLD_LPC_I2C_CH2_OFF) | \ +- MLXPLAT_CPLD_LPC_PIO_OFFSET) +- +-/* Start channel numbers */ +-#define MLXPLAT_CPLD_CH1 2 +-#define MLXPLAT_CPLD_CH2 10 +- +-/* Number of LPC attached MUX platform devices */ +-#define MLXPLAT_CPLD_LPC_MUX_DEVS 2 +- +-/* mlxplat_priv - platform private data +- * @pdev_i2c - i2c controller platform device +- * @pdev_mux - array of mux platform devices +- */ +-struct mlxplat_priv { +- struct platform_device *pdev_i2c; +- struct platform_device *pdev_mux[MLXPLAT_CPLD_LPC_MUX_DEVS]; +-}; +- +-/* Regions for LPC I2C controller and LPC base register space */ +-static const struct resource mlxplat_lpc_resources[] = { +- [0] = DEFINE_RES_NAMED(MLXPLAT_CPLD_LPC_I2C_BASE_ADRR, +- MLXPLAT_CPLD_LPC_IO_RANGE, +- "mlxplat_cpld_lpc_i2c_ctrl", IORESOURCE_IO), +- [1] = DEFINE_RES_NAMED(MLXPLAT_CPLD_LPC_REG_BASE_ADRR, +- MLXPLAT_CPLD_LPC_IO_RANGE, +- "mlxplat_cpld_lpc_regs", +- IORESOURCE_IO), +-}; +- +-/* Platform default channels */ +-static const int mlxplat_default_channels[][8] = { +- { +- MLXPLAT_CPLD_CH1, MLXPLAT_CPLD_CH1 + 1, MLXPLAT_CPLD_CH1 + 2, +- MLXPLAT_CPLD_CH1 + 3, MLXPLAT_CPLD_CH1 + 4, MLXPLAT_CPLD_CH1 + +- 5, MLXPLAT_CPLD_CH1 + 6, MLXPLAT_CPLD_CH1 + 7 +- }, +- { +- MLXPLAT_CPLD_CH2, MLXPLAT_CPLD_CH2 + 1, MLXPLAT_CPLD_CH2 + 2, +- MLXPLAT_CPLD_CH2 + 3, MLXPLAT_CPLD_CH2 + 4, MLXPLAT_CPLD_CH2 + +- 5, MLXPLAT_CPLD_CH2 + 6, MLXPLAT_CPLD_CH2 + 7 +- }, +-}; +- +-/* Platform channels for MSN21xx system family */ +-static const int mlxplat_msn21xx_channels[] = { 1, 2, 3, 4, 5, 6, 7, 8 }; +- +-/* Platform mux data */ +-static struct i2c_mux_reg_platform_data mlxplat_mux_data[] = { +- { +- .parent = 1, +- .base_nr = MLXPLAT_CPLD_CH1, +- .write_only = 1, +- .reg = (void __iomem *)MLXPLAT_CPLD_LPC_REG1, +- .reg_size = 1, +- .idle_in_use = 1, +- }, +- { +- .parent = 1, +- .base_nr = MLXPLAT_CPLD_CH2, +- .write_only = 1, +- .reg = (void __iomem *)MLXPLAT_CPLD_LPC_REG2, +- .reg_size = 1, +- .idle_in_use = 1, +- }, +- +-}; +- +-static struct platform_device *mlxplat_dev; +- +-static int __init mlxplat_dmi_default_matched(const struct dmi_system_id *dmi) +-{ +- int i; +- +- for (i = 0; i < ARRAY_SIZE(mlxplat_mux_data); i++) { +- mlxplat_mux_data[i].values = mlxplat_default_channels[i]; +- mlxplat_mux_data[i].n_values = +- ARRAY_SIZE(mlxplat_default_channels[i]); +- } +- +- return 1; +-}; +- +-static int __init mlxplat_dmi_msn21xx_matched(const struct dmi_system_id *dmi) +-{ +- int i; +- +- for (i = 0; i < ARRAY_SIZE(mlxplat_mux_data); i++) { +- mlxplat_mux_data[i].values = mlxplat_msn21xx_channels; +- mlxplat_mux_data[i].n_values = +- ARRAY_SIZE(mlxplat_msn21xx_channels); +- } +- +- return 1; +-}; +- +-static struct dmi_system_id mlxplat_dmi_table[] __initdata = { +- { +- .callback = mlxplat_dmi_default_matched, +- .matches = { +- DMI_MATCH(DMI_BOARD_VENDOR, "Mellanox Technologies"), +- DMI_MATCH(DMI_PRODUCT_NAME, "MSN24"), +- }, +- }, +- { +- .callback = mlxplat_dmi_default_matched, +- .matches = { +- DMI_MATCH(DMI_BOARD_VENDOR, "Mellanox Technologies"), +- DMI_MATCH(DMI_PRODUCT_NAME, "MSN27"), +- }, +- }, +- { +- .callback = mlxplat_dmi_default_matched, +- .matches = { +- DMI_MATCH(DMI_BOARD_VENDOR, "Mellanox Technologies"), +- DMI_MATCH(DMI_PRODUCT_NAME, "MSB"), +- }, +- }, +- { +- .callback = mlxplat_dmi_default_matched, +- .matches = { +- DMI_MATCH(DMI_BOARD_VENDOR, "Mellanox Technologies"), +- DMI_MATCH(DMI_PRODUCT_NAME, "MSX"), +- }, +- }, +- { +- .callback = mlxplat_dmi_msn21xx_matched, +- .matches = { +- DMI_MATCH(DMI_BOARD_VENDOR, "Mellanox Technologies"), +- DMI_MATCH(DMI_PRODUCT_NAME, "MSN21"), +- }, +- }, +- { } +-}; +- +-static int __init mlxplat_init(void) +-{ +- struct mlxplat_priv *priv; +- int i, err; +- +- if (!dmi_check_system(mlxplat_dmi_table)) +- return -ENODEV; +- +- mlxplat_dev = platform_device_register_simple(MLX_PLAT_DEVICE_NAME, -1, +- mlxplat_lpc_resources, +- ARRAY_SIZE(mlxplat_lpc_resources)); +- +- if (IS_ERR(mlxplat_dev)) +- return PTR_ERR(mlxplat_dev); +- +- priv = devm_kzalloc(&mlxplat_dev->dev, sizeof(struct mlxplat_priv), +- GFP_KERNEL); +- if (!priv) { +- err = -ENOMEM; +- goto fail_alloc; +- } +- platform_set_drvdata(mlxplat_dev, priv); +- +- priv->pdev_i2c = platform_device_register_simple("i2c_mlxcpld", -1, +- NULL, 0); +- if (IS_ERR(priv->pdev_i2c)) { +- err = PTR_ERR(priv->pdev_i2c); +- goto fail_alloc; +- }; +- +- for (i = 0; i < ARRAY_SIZE(mlxplat_mux_data); i++) { +- priv->pdev_mux[i] = platform_device_register_resndata( +- &mlxplat_dev->dev, +- "i2c-mux-reg", i, NULL, +- 0, &mlxplat_mux_data[i], +- sizeof(mlxplat_mux_data[i])); +- if (IS_ERR(priv->pdev_mux[i])) { +- err = PTR_ERR(priv->pdev_mux[i]); +- goto fail_platform_mux_register; +- } +- } +- +- return 0; +- +-fail_platform_mux_register: +- while (--i >= 0) +- platform_device_unregister(priv->pdev_mux[i]); +- platform_device_unregister(priv->pdev_i2c); +-fail_alloc: +- platform_device_unregister(mlxplat_dev); +- +- return err; +-} +-module_init(mlxplat_init); +- +-static void __exit mlxplat_exit(void) +-{ +- struct mlxplat_priv *priv = platform_get_drvdata(mlxplat_dev); +- int i; +- +- for (i = ARRAY_SIZE(mlxplat_mux_data) - 1; i >= 0 ; i--) +- platform_device_unregister(priv->pdev_mux[i]); +- +- platform_device_unregister(priv->pdev_i2c); +- platform_device_unregister(mlxplat_dev); +-} +-module_exit(mlxplat_exit); +- +-MODULE_AUTHOR("Vadim Pasternak (vadimp@mellanox.com)"); +-MODULE_DESCRIPTION("Mellanox platform driver"); +-MODULE_LICENSE("Dual BSD/GPL"); +-MODULE_ALIAS("dmi:*:*Mellanox*:MSN24*:"); +-MODULE_ALIAS("dmi:*:*Mellanox*:MSN27*:"); +-MODULE_ALIAS("dmi:*:*Mellanox*:MSB*:"); +-MODULE_ALIAS("dmi:*:*Mellanox*:MSX*:"); +-MODULE_ALIAS("dmi:*:*Mellanox*:MSN21*:"); +diff -Nur a/drivers/platform/Kconfig b/drivers/platform/Kconfig +--- a/drivers/platform/Kconfig 2017-11-12 11:01:23.768177498 +0000 ++++ b/drivers/platform/Kconfig 2017-11-12 11:01:47.784177817 +0000 +@@ -8,3 +8,5 @@ + source "drivers/platform/goldfish/Kconfig" + + source "drivers/platform/chrome/Kconfig" ++ ++source "drivers/platform/mellanox/Kconfig" +diff -Nur a/drivers/platform/Makefile b/drivers/platform/Makefile +--- a/drivers/platform/Makefile 2017-11-12 11:01:23.768177498 +0000 ++++ b/drivers/platform/Makefile 2017-11-12 11:01:47.784177817 +0000 +@@ -3,6 +3,7 @@ + # + + obj-$(CONFIG_X86) += x86/ ++obj-$(CONFIG_MELLANOX_PLATFORM) += mellanox/ + obj-$(CONFIG_MIPS) += mips/ + obj-$(CONFIG_OLPC) += olpc/ + obj-$(CONFIG_GOLDFISH) += goldfish/ +diff -Nur a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig +--- a/drivers/platform/x86/Kconfig 2017-11-12 11:03:10.600178917 +0000 ++++ b/drivers/platform/x86/Kconfig 2017-11-12 11:03:56.776179530 +0000 +@@ -1027,4 +1027,17 @@ + used to get various SoC events and parameters + directly via debugfs files. Various tools may use + this interface for SoC state monitoring. ++ ++config MLX_PLATFORM ++ tristate "Mellanox Technologies platform support" ++ depends on X86_64 ++ ---help--- ++ This option enables system support for the Mellanox Technologies ++ platform. The Mellanox systems provide data center networking ++ solutions based on Virtual Protocol Interconnect (VPI) technology ++ enable seamless connectivity to 56/100Gb/s InfiniBand or 10/40/56GbE ++ connection. ++ ++ If you have a Mellanox system, say Y or M here. ++ + endif # X86_PLATFORM_DEVICES +diff -Nur a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile +--- a/drivers/platform/x86/Makefile 2017-11-12 11:03:10.600178917 +0000 ++++ b/drivers/platform/x86/Makefile 2017-11-12 11:03:56.776179530 +0000 +@@ -71,3 +71,4 @@ + intel_telemetry_pltdrv.o \ + intel_telemetry_debugfs.o + obj-$(CONFIG_INTEL_PMC_CORE) += intel_pmc_core.o ++obj-$(CONFIG_MLX_PLATFORM) += mlx-platform.o +diff -Nur a/drivers/platform/x86/mlx-platform.c b/drivers/platform/x86/mlx-platform.c +--- a/drivers/platform/x86/mlx-platform.c 1970-01-01 00:00:00.000000000 +0000 ++++ b/drivers/platform/x86/mlx-platform.c 2017-11-12 11:03:56.776179530 +0000 +@@ -0,0 +1,1090 @@ ++/* ++ * Copyright (c) 2016 Mellanox Technologies. All rights reserved. ++ * Copyright (c) 2016 Vadim Pasternak ++ * ++ * Redistribution and use in source and binary forms, with or without ++ * modification, are permitted provided that the following conditions are met: ++ * ++ * 1. Redistributions of source code must retain the above copyright ++ * notice, this list of conditions and the following disclaimer. ++ * 2. Redistributions in binary form must reproduce the above copyright ++ * notice, this list of conditions and the following disclaimer in the ++ * documentation and/or other materials provided with the distribution. ++ * 3. Neither the names of the copyright holders nor the names of its ++ * contributors may be used to endorse or promote products derived from ++ * this software without specific prior written permission. ++ * ++ * Alternatively, this software may be distributed under the terms of the ++ * GNU General Public License ("GPL") version 2 as published by the Free ++ * Software Foundation. ++ * ++ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" ++ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE ++ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ++ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE ++ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR ++ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF ++ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS ++ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN ++ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ++ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE ++ * POSSIBILITY OF SUCH DAMAGE. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#define MLX_PLAT_DEVICE_NAME "mlxplat" ++ ++/* LPC bus IO offsets */ ++#define MLXPLAT_CPLD_LPC_I2C_BASE_ADRR 0x2000 ++#define MLXPLAT_CPLD_LPC_REG_BASE_ADRR 0x2500 ++#define MLXPLAT_CPLD_LPC_REG_LED1_OFF 0x20 ++#define MLXPLAT_CPLD_LPC_REG_LED2_OFF 0x21 ++#define MLXPLAT_CPLD_LPC_REG_LED3_OFF 0x22 ++#define MLXPLAT_CPLD_LPC_REG_LED4_OFF 0x23 ++#define MLXPLAT_CPLD_LPC_REG_LED5_OFF 0x24 ++#define MLXPLAT_CPLD_LPC_REG_AGGR_OFF 0x3a ++#define MLXPLAT_CPLD_LPC_REG_AGGR_LOW_OFF 0x40 ++#define MLXPLAT_CPLD_LPC_REG_PSU_OFF 0x58 ++#define MLXPLAT_CPLD_LPC_REG_PWR_OFF 0x64 ++#define MLXPLAT_CPLD_LPC_REG_FAN_OFF 0x88 ++#define MLXPLAT_CPLD_LPC_IO_RANGE 0x100 ++#define MLXPLAT_CPLD_LPC_I2C_CH1_OFF 0xdb ++#define MLXPLAT_CPLD_LPC_I2C_CH2_OFF 0xda ++#define MLXPLAT_CPLD_LPC_PIO_OFFSET 0x10000UL ++#define MLXPLAT_CPLD_LPC_REG1 ((MLXPLAT_CPLD_LPC_REG_BASE_ADRR + \ ++ MLXPLAT_CPLD_LPC_I2C_CH1_OFF) | \ ++ MLXPLAT_CPLD_LPC_PIO_OFFSET) ++#define MLXPLAT_CPLD_LPC_REG2 ((MLXPLAT_CPLD_LPC_REG_BASE_ADRR + \ ++ MLXPLAT_CPLD_LPC_I2C_CH2_OFF) | \ ++ MLXPLAT_CPLD_LPC_PIO_OFFSET) ++ ++/* Masks for aggregation, psu, pwr and fan event in CPLD related registers. */ ++#define MLXPLAT_CPLD_AGGR_PSU_MASK_DEF 0x08 ++#define MLXPLAT_CPLD_AGGR_PWR_MASK_DEF 0x08 ++#define MLXPLAT_CPLD_AGGR_FAN_MASK_DEF 0x40 ++#define MLXPLAT_CPLD_AGGR_MASK_DEF (MLXPLAT_CPLD_AGGR_PSU_MASK_DEF | \ ++ MLXPLAT_CPLD_AGGR_FAN_MASK_DEF) ++#define MLXPLAT_CPLD_AGGR_MASK_NG_DEF 0x04 ++#define MLXPLAT_CPLD_LOW_AGGR_MASK_LOW 0xc0 ++#define MLXPLAT_CPLD_AGGR_MASK_MSN21XX 0x04 ++#define MLXPLAT_CPLD_PSU_MASK GENMASK(1, 0) ++#define MLXPLAT_CPLD_PWR_MASK GENMASK(1, 0) ++#define MLXPLAT_CPLD_FAN_MASK GENMASK(3, 0) ++#define MLXPLAT_CPLD_FAN_NG_MASK GENMASK(5, 0) ++#define MLXPLAT_CPLD_LED_LO_NIBBLE_MASK GENMASK(7, 4) ++#define MLXPLAT_CPLD_LED_HI_NIBBLE_MASK GENMASK(3, 0) ++ ++/* Start channel numbers */ ++#define MLXPLAT_CPLD_CH1 2 ++#define MLXPLAT_CPLD_CH2 10 ++ ++/* Number of LPC attached MUX platform devices */ ++#define MLXPLAT_CPLD_LPC_MUX_DEVS 2 ++ ++/* PSU adapter numbers */ ++#define MLXPLAT_CPLD_PSU_DEFAULT_NR 10 ++#define MLXPLAT_CPLD_PSU_MSNXXXX_NR 4 ++ ++/* mlxplat_priv - platform private data ++ * @pdev_i2c - i2c controller platform device ++ * @pdev_mux - array of mux platform devices ++ * @pdev_hotplug - hotplug platform devices ++ * @pdev_led - led platform devices ++ */ ++struct mlxplat_priv { ++ struct platform_device *pdev_i2c; ++ struct platform_device *pdev_mux[MLXPLAT_CPLD_LPC_MUX_DEVS]; ++ struct platform_device *pdev_hotplug; ++ struct platform_device *pdev_led; ++}; ++ ++/* Regions for LPC I2C controller and LPC base register space */ ++static const struct resource mlxplat_lpc_resources[] = { ++ [0] = DEFINE_RES_NAMED(MLXPLAT_CPLD_LPC_I2C_BASE_ADRR, ++ MLXPLAT_CPLD_LPC_IO_RANGE, ++ "mlxplat_cpld_lpc_i2c_ctrl", IORESOURCE_IO), ++ [1] = DEFINE_RES_NAMED(MLXPLAT_CPLD_LPC_REG_BASE_ADRR, ++ MLXPLAT_CPLD_LPC_IO_RANGE, ++ "mlxplat_cpld_lpc_regs", ++ IORESOURCE_IO), ++}; ++ ++/* Platform default channels */ ++static const int mlxplat_default_channels[][8] = { ++ { ++ MLXPLAT_CPLD_CH1, MLXPLAT_CPLD_CH1 + 1, MLXPLAT_CPLD_CH1 + 2, ++ MLXPLAT_CPLD_CH1 + 3, MLXPLAT_CPLD_CH1 + 4, MLXPLAT_CPLD_CH1 + ++ 5, MLXPLAT_CPLD_CH1 + 6, MLXPLAT_CPLD_CH1 + 7 ++ }, ++ { ++ MLXPLAT_CPLD_CH2, MLXPLAT_CPLD_CH2 + 1, MLXPLAT_CPLD_CH2 + 2, ++ MLXPLAT_CPLD_CH2 + 3, MLXPLAT_CPLD_CH2 + 4, MLXPLAT_CPLD_CH2 + ++ 5, MLXPLAT_CPLD_CH2 + 6, MLXPLAT_CPLD_CH2 + 7 ++ }, ++}; ++ ++/* Platform channels for MSN21xx system family */ ++static const int mlxplat_msn21xx_channels[] = { 1, 2, 3, 4, 5, 6, 7, 8 }; ++ ++/* Platform mux data */ ++static struct i2c_mux_reg_platform_data mlxplat_mux_data[] = { ++ { ++ .parent = 1, ++ .base_nr = MLXPLAT_CPLD_CH1, ++ .write_only = 1, ++ .reg = (void __iomem *)MLXPLAT_CPLD_LPC_REG1, ++ .reg_size = 1, ++ .idle_in_use = 1, ++ }, ++ { ++ .parent = 1, ++ .base_nr = MLXPLAT_CPLD_CH2, ++ .write_only = 1, ++ .reg = (void __iomem *)MLXPLAT_CPLD_LPC_REG2, ++ .reg_size = 1, ++ .idle_in_use = 1, ++ }, ++ ++}; ++ ++/* Platform hotplug devices */ ++static struct i2c_board_info mlxplat_mlxcpld_psu[] = { ++ { ++ I2C_BOARD_INFO("24c02", 0x51), ++ }, ++ { ++ I2C_BOARD_INFO("24c02", 0x50), ++ }, ++}; ++ ++static struct i2c_board_info mlxplat_mlxcpld_pwr[] = { ++ { ++ I2C_BOARD_INFO("dps460", 0x59), ++ }, ++ { ++ I2C_BOARD_INFO("dps460", 0x58), ++ }, ++}; ++ ++static struct i2c_board_info mlxplat_mlxcpld_fan[] = { ++ { ++ I2C_BOARD_INFO("24c32", 0x50), ++ }, ++ { ++ I2C_BOARD_INFO("24c32", 0x50), ++ }, ++ { ++ I2C_BOARD_INFO("24c32", 0x50), ++ }, ++ { ++ I2C_BOARD_INFO("24c32", 0x50), ++ }, ++}; ++ ++/* Platform hotplug default data */ ++static struct mlxreg_core_data mlxplat_mlxcpld_default_psu_items_data[] = { ++ { ++ .label = "psu1", ++ .reg = MLXPLAT_CPLD_LPC_REG_PSU_OFF, ++ .mask = BIT(0), ++ .hpdev.brdinfo = &mlxplat_mlxcpld_psu[0], ++ .hpdev.nr = MLXPLAT_CPLD_PSU_DEFAULT_NR, ++ }, ++ { ++ .label = "psu2", ++ .reg = MLXPLAT_CPLD_LPC_REG_PSU_OFF, ++ .mask = BIT(1), ++ .hpdev.brdinfo = &mlxplat_mlxcpld_psu[1], ++ .hpdev.nr = MLXPLAT_CPLD_PSU_DEFAULT_NR, ++ }, ++}; ++ ++static struct mlxreg_core_data mlxplat_mlxcpld_default_pwr_items_data[] = { ++ { ++ .label = "pwr1", ++ .reg = MLXPLAT_CPLD_LPC_REG_PWR_OFF, ++ .mask = BIT(0), ++ .hpdev.brdinfo = &mlxplat_mlxcpld_pwr[0], ++ .hpdev.nr = MLXPLAT_CPLD_PSU_DEFAULT_NR, ++ }, ++ { ++ .label = "pwr2", ++ .reg = MLXPLAT_CPLD_LPC_REG_PWR_OFF, ++ .mask = BIT(1), ++ .hpdev.brdinfo = &mlxplat_mlxcpld_pwr[1], ++ .hpdev.nr = MLXPLAT_CPLD_PSU_DEFAULT_NR, ++ }, ++}; ++ ++static struct mlxreg_core_data mlxplat_mlxcpld_default_fan_items_data[] = { ++ { ++ .label = "fan1", ++ .reg = MLXPLAT_CPLD_LPC_REG_FAN_OFF, ++ .mask = BIT(0), ++ .hpdev.brdinfo = &mlxplat_mlxcpld_fan[0], ++ .hpdev.nr = 11, ++ }, ++ { ++ .label = "fan2", ++ .reg = MLXPLAT_CPLD_LPC_REG_FAN_OFF, ++ .mask = BIT(1), ++ .hpdev.brdinfo = &mlxplat_mlxcpld_fan[1], ++ .hpdev.nr = 12, ++ }, ++ { ++ .label = "fan3", ++ .reg = MLXPLAT_CPLD_LPC_REG_FAN_OFF, ++ .mask = BIT(2), ++ .hpdev.brdinfo = &mlxplat_mlxcpld_fan[2], ++ .hpdev.nr = 13, ++ }, ++ { ++ .label = "fan4", ++ .reg = MLXPLAT_CPLD_LPC_REG_FAN_OFF, ++ .mask = BIT(3), ++ .hpdev.brdinfo = &mlxplat_mlxcpld_fan[3], ++ .hpdev.nr = 14, ++ }, ++}; ++ ++static struct mlxreg_core_item mlxplat_mlxcpld_default_items[] = { ++ { ++ .data = mlxplat_mlxcpld_default_psu_items_data, ++ .aggr_mask = MLXPLAT_CPLD_AGGR_PSU_MASK_DEF, ++ .reg = MLXPLAT_CPLD_LPC_REG_PSU_OFF, ++ .mask = MLXPLAT_CPLD_PSU_MASK, ++ .count = ARRAY_SIZE(mlxplat_mlxcpld_psu), ++ .inversed = 1, ++ .health = false, ++ }, ++ { ++ .data = mlxplat_mlxcpld_default_pwr_items_data, ++ .aggr_mask = MLXPLAT_CPLD_AGGR_PWR_MASK_DEF, ++ .reg = MLXPLAT_CPLD_LPC_REG_PWR_OFF, ++ .mask = MLXPLAT_CPLD_PWR_MASK, ++ .count = ARRAY_SIZE(mlxplat_mlxcpld_pwr), ++ .inversed = 0, ++ .health = false, ++ }, ++ { ++ .data = mlxplat_mlxcpld_default_fan_items_data, ++ .aggr_mask = MLXPLAT_CPLD_AGGR_FAN_MASK_DEF, ++ .reg = MLXPLAT_CPLD_LPC_REG_FAN_OFF, ++ .mask = MLXPLAT_CPLD_FAN_MASK, ++ .count = ARRAY_SIZE(mlxplat_mlxcpld_fan), ++ .inversed = 1, ++ .health = false, ++ }, ++}; ++ ++static ++struct mlxreg_core_hotplug_platform_data mlxplat_mlxcpld_default_data = { ++ .items = mlxplat_mlxcpld_default_items, ++ .counter = ARRAY_SIZE(mlxplat_mlxcpld_default_items), ++ .cell = MLXPLAT_CPLD_LPC_REG_AGGR_OFF, ++ .mask = MLXPLAT_CPLD_AGGR_MASK_DEF, ++}; ++ ++/* Platform hotplug MSN21xx system family data */ ++static struct i2c_board_info mlxplat_mlxcpld_msn21xx_pwr[] = { ++ { ++ I2C_BOARD_INFO("holder", 0x59), ++ }, ++ { ++ I2C_BOARD_INFO("holder", 0x58), ++ }, ++}; ++ ++static struct mlxreg_core_data mlxplat_mlxcpld_msn21xx_pwr_items_data[] = { ++ { ++ .label = "pwr1", ++ .reg = MLXPLAT_CPLD_LPC_REG_PWR_OFF, ++ .mask = BIT(0), ++ .hpdev.brdinfo = &mlxplat_mlxcpld_msn21xx_pwr[0], ++ .hpdev.nr = MLXPLAT_CPLD_PSU_DEFAULT_NR, ++ }, ++ { ++ .label = "pwr2", ++ .reg = MLXPLAT_CPLD_LPC_REG_PWR_OFF, ++ .mask = BIT(1), ++ .hpdev.brdinfo = &mlxplat_mlxcpld_msn21xx_pwr[1], ++ .hpdev.nr = MLXPLAT_CPLD_PSU_DEFAULT_NR, ++ }, ++}; ++ ++static struct mlxreg_core_item mlxplat_mlxcpld_msn21xx_items[] = { ++ { ++ .data = mlxplat_mlxcpld_msn21xx_pwr_items_data, ++ .aggr_mask = MLXPLAT_CPLD_AGGR_PWR_MASK_DEF, ++ .reg = MLXPLAT_CPLD_LPC_REG_PWR_OFF, ++ .mask = MLXPLAT_CPLD_PWR_MASK, ++ .count = ARRAY_SIZE(mlxplat_mlxcpld_msn21xx_pwr_items_data), ++ .inversed = 0, ++ .health = false, ++ }, ++}; ++ ++static ++struct mlxreg_core_hotplug_platform_data mlxplat_mlxcpld_msn21xx_data = { ++ .items = mlxplat_mlxcpld_msn21xx_items, ++ .counter = ARRAY_SIZE(mlxplat_mlxcpld_msn21xx_items), ++ .cell = MLXPLAT_CPLD_LPC_REG_AGGR_OFF, ++ .mask = MLXPLAT_CPLD_AGGR_MASK_DEF, ++ .cell_low = MLXPLAT_CPLD_LPC_REG_AGGR_LOW_OFF, ++ .mask_low = MLXPLAT_CPLD_LOW_AGGR_MASK_LOW, ++}; ++ ++/* Platform hotplug MSN201x system family data */ ++static struct mlxreg_core_data mlxplat_mlxcpld_msn201x_pwr_items_data[] = { ++ { ++ .label = "pwr1", ++ .reg = MLXPLAT_CPLD_LPC_REG_PWR_OFF, ++ .mask = BIT(0), ++ .hpdev.brdinfo = &mlxplat_mlxcpld_msn21xx_pwr[0], ++ .hpdev.nr = MLXPLAT_CPLD_PSU_MSNXXXX_NR, ++ }, ++ { ++ .label = "pwr2", ++ .reg = MLXPLAT_CPLD_LPC_REG_PWR_OFF, ++ .mask = BIT(1), ++ .hpdev.brdinfo = &mlxplat_mlxcpld_msn21xx_pwr[1], ++ .hpdev.nr = MLXPLAT_CPLD_PSU_MSNXXXX_NR, ++ }, ++}; ++ ++static struct mlxreg_core_item mlxplat_mlxcpld_msn201x_items[] = { ++ { ++ .data = mlxplat_mlxcpld_msn201x_pwr_items_data, ++ .aggr_mask = MLXPLAT_CPLD_AGGR_PWR_MASK_DEF, ++ .reg = MLXPLAT_CPLD_LPC_REG_PWR_OFF, ++ .mask = MLXPLAT_CPLD_PWR_MASK, ++ .count = ARRAY_SIZE(mlxplat_mlxcpld_msn201x_pwr_items_data), ++ .inversed = 0, ++ .health = false, ++ }, ++}; ++ ++static ++struct mlxreg_core_hotplug_platform_data mlxplat_mlxcpld_msn201x_data = { ++ .items = mlxplat_mlxcpld_msn21xx_items, ++ .counter = ARRAY_SIZE(mlxplat_mlxcpld_msn201x_items), ++ .cell = MLXPLAT_CPLD_LPC_REG_AGGR_OFF, ++ .mask = MLXPLAT_CPLD_AGGR_MASK_DEF, ++ .cell_low = MLXPLAT_CPLD_LPC_REG_AGGR_LOW_OFF, ++ .mask_low = MLXPLAT_CPLD_LOW_AGGR_MASK_LOW, ++}; ++ ++/* Platform hotplug next generation system family data */ ++static struct i2c_board_info mlxplat_mlxcpld_ng_fan = { ++ I2C_BOARD_INFO("holder", 0x50), ++}; ++ ++static struct mlxreg_core_data mlxplat_mlxcpld_default_ng_psu_items_data[] = { ++ { ++ .label = "psu1", ++ .reg = MLXPLAT_CPLD_LPC_REG_PSU_OFF, ++ .mask = BIT(0), ++ .hpdev.brdinfo = &mlxplat_mlxcpld_psu[0], ++ .hpdev.nr = MLXPLAT_CPLD_PSU_MSNXXXX_NR, ++ }, ++ { ++ .label = "psu2", ++ .reg = MLXPLAT_CPLD_LPC_REG_PSU_OFF, ++ .mask = BIT(1), ++ .hpdev.brdinfo = &mlxplat_mlxcpld_psu[1], ++ .hpdev.nr = MLXPLAT_CPLD_PSU_MSNXXXX_NR, ++ }, ++}; ++ ++static struct mlxreg_core_data mlxplat_mlxcpld_default_ng_pwr_items_data[] = { ++ { ++ .label = "pwr1", ++ .reg = MLXPLAT_CPLD_LPC_REG_PWR_OFF, ++ .mask = BIT(0), ++ .hpdev.brdinfo = &mlxplat_mlxcpld_pwr[0], ++ .hpdev.nr = MLXPLAT_CPLD_PSU_MSNXXXX_NR, ++ }, ++ { ++ .label = "pwr2", ++ .reg = MLXPLAT_CPLD_LPC_REG_PWR_OFF, ++ .mask = BIT(1), ++ .hpdev.brdinfo = &mlxplat_mlxcpld_pwr[1], ++ .hpdev.nr = MLXPLAT_CPLD_PSU_MSNXXXX_NR, ++ }, ++}; ++ ++static struct mlxreg_core_data mlxplat_mlxcpld_default_ng_fan_items_data[] = { ++ { ++ .label = "fan1", ++ .reg = MLXPLAT_CPLD_LPC_REG_FAN_OFF, ++ .mask = BIT(0), ++ .hpdev.brdinfo = &mlxplat_mlxcpld_ng_fan, ++ .hpdev.nr = 11, ++ }, ++ { ++ .label = "fan2", ++ .reg = MLXPLAT_CPLD_LPC_REG_FAN_OFF, ++ .mask = BIT(1), ++ .hpdev.brdinfo = &mlxplat_mlxcpld_ng_fan, ++ .hpdev.nr = 12, ++ }, ++ { ++ .label = "fan3", ++ .reg = MLXPLAT_CPLD_LPC_REG_FAN_OFF, ++ .mask = BIT(2), ++ .hpdev.brdinfo = &mlxplat_mlxcpld_ng_fan, ++ .hpdev.nr = 13, ++ }, ++ { ++ .label = "fan4", ++ .reg = MLXPLAT_CPLD_LPC_REG_FAN_OFF, ++ .mask = BIT(3), ++ .hpdev.brdinfo = &mlxplat_mlxcpld_ng_fan, ++ .hpdev.nr = 14, ++ }, ++ { ++ .label = "fan5", ++ .reg = MLXPLAT_CPLD_LPC_REG_FAN_OFF, ++ .mask = BIT(3), ++ .hpdev.brdinfo = &mlxplat_mlxcpld_ng_fan, ++ .hpdev.nr = 15, ++ }, ++ { ++ .label = "fan6", ++ .reg = MLXPLAT_CPLD_LPC_REG_FAN_OFF, ++ .mask = BIT(3), ++ .hpdev.brdinfo = &mlxplat_mlxcpld_ng_fan, ++ .hpdev.nr = 16, ++ }, ++}; ++ ++static struct mlxreg_core_item mlxplat_mlxcpld_default_ng_items[] = { ++ { ++ .data = mlxplat_mlxcpld_default_ng_psu_items_data, ++ .aggr_mask = MLXPLAT_CPLD_AGGR_MASK_NG_DEF, ++ .reg = MLXPLAT_CPLD_LPC_REG_PSU_OFF, ++ .mask = MLXPLAT_CPLD_PSU_MASK, ++ .count = ARRAY_SIZE(mlxplat_mlxcpld_psu), ++ .inversed = 1, ++ .health = false, ++ }, ++ { ++ .data = mlxplat_mlxcpld_default_ng_pwr_items_data, ++ .aggr_mask = MLXPLAT_CPLD_AGGR_MASK_NG_DEF, ++ .reg = MLXPLAT_CPLD_LPC_REG_PWR_OFF, ++ .mask = MLXPLAT_CPLD_PWR_MASK, ++ .count = ARRAY_SIZE(mlxplat_mlxcpld_pwr), ++ .inversed = 0, ++ .health = false, ++ }, ++ { ++ .data = mlxplat_mlxcpld_default_ng_fan_items_data, ++ .aggr_mask = MLXPLAT_CPLD_AGGR_MASK_NG_DEF, ++ .reg = MLXPLAT_CPLD_LPC_REG_FAN_OFF, ++ .mask = MLXPLAT_CPLD_FAN_MASK, ++ .count = ARRAY_SIZE(mlxplat_mlxcpld_default_ng_fan_items_data), ++ .inversed = 1, ++ .health = false, ++ }, ++}; ++ ++static ++struct mlxreg_core_hotplug_platform_data mlxplat_mlxcpld_default_ng_data = { ++ .items = mlxplat_mlxcpld_default_ng_items, ++ .counter = ARRAY_SIZE(mlxplat_mlxcpld_default_ng_items), ++ .cell = MLXPLAT_CPLD_LPC_REG_AGGR_OFF, ++ .mask = MLXPLAT_CPLD_AGGR_MASK_NG_DEF, ++ .cell_low = MLXPLAT_CPLD_LPC_REG_AGGR_LOW_OFF, ++ .mask_low = MLXPLAT_CPLD_LOW_AGGR_MASK_LOW, ++}; ++ ++static struct mlxreg_core_data mlxplat_mlxcpld_msn274x_fan_items_data[] = { ++ { ++ .label = "fan1", ++ .reg = MLXPLAT_CPLD_LPC_REG_FAN_OFF, ++ .mask = BIT(0), ++ .hpdev.brdinfo = &mlxplat_mlxcpld_ng_fan, ++ .hpdev.nr = 11, ++ }, ++ { ++ .label = "fan2", ++ .reg = MLXPLAT_CPLD_LPC_REG_FAN_OFF, ++ .mask = BIT(1), ++ .hpdev.brdinfo = &mlxplat_mlxcpld_ng_fan, ++ .hpdev.nr = 12, ++ }, ++ { ++ .label = "fan3", ++ .reg = MLXPLAT_CPLD_LPC_REG_FAN_OFF, ++ .mask = BIT(2), ++ .hpdev.brdinfo = &mlxplat_mlxcpld_ng_fan, ++ .hpdev.nr = 13, ++ }, ++ { ++ .label = "fan4", ++ .reg = MLXPLAT_CPLD_LPC_REG_FAN_OFF, ++ .mask = BIT(3), ++ .hpdev.brdinfo = &mlxplat_mlxcpld_ng_fan, ++ .hpdev.nr = 14, ++ }, ++}; ++ ++static struct mlxreg_core_item mlxplat_mlxcpld_msn274x_items[] = { ++ { ++ .data = mlxplat_mlxcpld_default_ng_psu_items_data, ++ .aggr_mask = MLXPLAT_CPLD_AGGR_MASK_NG_DEF, ++ .reg = MLXPLAT_CPLD_LPC_REG_PSU_OFF, ++ .mask = MLXPLAT_CPLD_PSU_MASK, ++ .count = ARRAY_SIZE(mlxplat_mlxcpld_psu), ++ .inversed = 1, ++ .health = false, ++ }, ++ { ++ .data = mlxplat_mlxcpld_default_ng_pwr_items_data, ++ .aggr_mask = MLXPLAT_CPLD_AGGR_MASK_NG_DEF, ++ .reg = MLXPLAT_CPLD_LPC_REG_PWR_OFF, ++ .mask = MLXPLAT_CPLD_PWR_MASK, ++ .count = ARRAY_SIZE(mlxplat_mlxcpld_pwr), ++ .inversed = 0, ++ .health = false, ++ }, ++ { ++ .data = mlxplat_mlxcpld_msn274x_fan_items_data, ++ .aggr_mask = MLXPLAT_CPLD_AGGR_MASK_NG_DEF, ++ .reg = MLXPLAT_CPLD_LPC_REG_FAN_OFF, ++ .mask = MLXPLAT_CPLD_FAN_MASK, ++ .count = ARRAY_SIZE(mlxplat_mlxcpld_msn274x_fan_items_data), ++ .inversed = 1, ++ .health = false, ++ }, ++}; ++ ++static ++struct mlxreg_core_hotplug_platform_data mlxplat_mlxcpld_msn274x_data = { ++ .items = mlxplat_mlxcpld_msn274x_items, ++ .counter = ARRAY_SIZE(mlxplat_mlxcpld_default_ng_items), ++ .cell = MLXPLAT_CPLD_LPC_REG_AGGR_OFF, ++ .mask = MLXPLAT_CPLD_AGGR_MASK_NG_DEF, ++ .cell_low = MLXPLAT_CPLD_LPC_REG_AGGR_LOW_OFF, ++ .mask_low = MLXPLAT_CPLD_LOW_AGGR_MASK_LOW, ++}; ++ ++/* Platform led default data */ ++static struct mlxreg_core_data mlxplat_mlxcpld_default_led_data[] = { ++ { ++ .label = "status:green", ++ .reg = MLXPLAT_CPLD_LPC_REG_LED1_OFF, ++ .mask = MLXPLAT_CPLD_LED_LO_NIBBLE_MASK, ++ }, ++ { ++ .label = "status:red", ++ .reg = MLXPLAT_CPLD_LPC_REG_LED1_OFF, ++ .mask = MLXPLAT_CPLD_LED_LO_NIBBLE_MASK ++ }, ++ { ++ .label = "psu:green", ++ .reg = MLXPLAT_CPLD_LPC_REG_LED1_OFF, ++ .mask = MLXPLAT_CPLD_LED_HI_NIBBLE_MASK, ++ }, ++ { ++ .label = "psu:red", ++ .reg = MLXPLAT_CPLD_LPC_REG_LED1_OFF, ++ .mask = MLXPLAT_CPLD_LED_HI_NIBBLE_MASK, ++ }, ++ { ++ .label = "fan1:green", ++ .reg = MLXPLAT_CPLD_LPC_REG_LED2_OFF, ++ .mask = MLXPLAT_CPLD_LED_LO_NIBBLE_MASK, ++ }, ++ { ++ .label = "fan1:red", ++ .reg = MLXPLAT_CPLD_LPC_REG_LED2_OFF, ++ .mask = MLXPLAT_CPLD_LED_LO_NIBBLE_MASK, ++ }, ++ { ++ .label = "fan2:green", ++ .reg = MLXPLAT_CPLD_LPC_REG_LED2_OFF, ++ .mask = MLXPLAT_CPLD_LED_HI_NIBBLE_MASK, ++ }, ++ { ++ .label = "fan2:red", ++ .reg = MLXPLAT_CPLD_LPC_REG_LED2_OFF, ++ .mask = MLXPLAT_CPLD_LED_HI_NIBBLE_MASK, ++ }, ++ { ++ .label = "fan3:green", ++ .reg = MLXPLAT_CPLD_LPC_REG_LED3_OFF, ++ .mask = MLXPLAT_CPLD_LED_LO_NIBBLE_MASK, ++ }, ++ { ++ .label = "fan3:red", ++ .reg = MLXPLAT_CPLD_LPC_REG_LED3_OFF, ++ .mask = MLXPLAT_CPLD_LED_LO_NIBBLE_MASK, ++ }, ++ { ++ .label = "fan4:green", ++ .reg = MLXPLAT_CPLD_LPC_REG_LED3_OFF, ++ .mask = MLXPLAT_CPLD_LED_HI_NIBBLE_MASK, ++ }, ++ { ++ .label = "fan4:red", ++ .reg = MLXPLAT_CPLD_LPC_REG_LED3_OFF, ++ .mask = MLXPLAT_CPLD_LED_HI_NIBBLE_MASK, ++ }, ++}; ++ ++static struct mlxreg_core_led_platform_data mlxplat_default_led_data = { ++ .data = mlxplat_mlxcpld_default_led_data, ++ .counter = ARRAY_SIZE(mlxplat_mlxcpld_default_led_data), ++}; ++ ++/* Platform led MSN21xx system family data */ ++static struct mlxreg_core_data mlxplat_mlxcpld_msn21xx_led_data[] = { ++ { ++ .label = "status:green", ++ .reg = MLXPLAT_CPLD_LPC_REG_LED1_OFF, ++ .mask = MLXPLAT_CPLD_LED_LO_NIBBLE_MASK, ++ }, ++ { ++ .label = "status:red", ++ .reg = MLXPLAT_CPLD_LPC_REG_LED1_OFF, ++ .mask = MLXPLAT_CPLD_LED_LO_NIBBLE_MASK ++ }, ++ { ++ .label = "fan:green", ++ .reg = MLXPLAT_CPLD_LPC_REG_LED2_OFF, ++ .mask = MLXPLAT_CPLD_LED_LO_NIBBLE_MASK, ++ }, ++ { ++ .label = "fan:red", ++ .reg = MLXPLAT_CPLD_LPC_REG_LED2_OFF, ++ .mask = MLXPLAT_CPLD_LED_LO_NIBBLE_MASK, ++ }, ++ { ++ .label = "psu1:green", ++ .reg = MLXPLAT_CPLD_LPC_REG_LED4_OFF, ++ .mask = MLXPLAT_CPLD_LED_LO_NIBBLE_MASK, ++ }, ++ { ++ .label = "psu1:red", ++ .reg = MLXPLAT_CPLD_LPC_REG_LED4_OFF, ++ .mask = MLXPLAT_CPLD_LED_LO_NIBBLE_MASK, ++ }, ++ { ++ .label = "psu2:green", ++ .reg = MLXPLAT_CPLD_LPC_REG_LED4_OFF, ++ .mask = MLXPLAT_CPLD_LED_HI_NIBBLE_MASK, ++ }, ++ { ++ .label = "psu2:red", ++ .reg = MLXPLAT_CPLD_LPC_REG_LED4_OFF, ++ .mask = MLXPLAT_CPLD_LED_HI_NIBBLE_MASK, ++ }, ++ { ++ .label = "uid:blue", ++ .reg = MLXPLAT_CPLD_LPC_REG_LED5_OFF, ++ .mask = MLXPLAT_CPLD_LED_LO_NIBBLE_MASK, ++ }, ++}; ++ ++static struct mlxreg_core_led_platform_data mlxplat_msn21xx_led_data = { ++ .data = mlxplat_mlxcpld_msn21xx_led_data, ++ .counter = ARRAY_SIZE(mlxplat_mlxcpld_msn21xx_led_data), ++}; ++ ++/* Platform led for default data for 200GbE systems */ ++static struct mlxreg_core_data mlxplat_mlxcpld_default_ng_led_data[] = { ++ { ++ .label = "status:green", ++ .reg = MLXPLAT_CPLD_LPC_REG_LED1_OFF, ++ .mask = MLXPLAT_CPLD_LED_LO_NIBBLE_MASK, ++ }, ++ { ++ .label = "status:orange", ++ .reg = MLXPLAT_CPLD_LPC_REG_LED1_OFF, ++ .mask = MLXPLAT_CPLD_LED_LO_NIBBLE_MASK ++ }, ++ { ++ .label = "psu:green", ++ .reg = MLXPLAT_CPLD_LPC_REG_LED1_OFF, ++ .mask = MLXPLAT_CPLD_LED_HI_NIBBLE_MASK, ++ }, ++ { ++ .label = "psu:orange", ++ .reg = MLXPLAT_CPLD_LPC_REG_LED1_OFF, ++ .mask = MLXPLAT_CPLD_LED_HI_NIBBLE_MASK, ++ }, ++ { ++ .label = "fan1:green", ++ .reg = MLXPLAT_CPLD_LPC_REG_LED2_OFF, ++ .mask = MLXPLAT_CPLD_LED_LO_NIBBLE_MASK, ++ }, ++ { ++ .label = "fan1:orange", ++ .reg = MLXPLAT_CPLD_LPC_REG_LED2_OFF, ++ .mask = MLXPLAT_CPLD_LED_LO_NIBBLE_MASK, ++ }, ++ { ++ .label = "fan2:green", ++ .reg = MLXPLAT_CPLD_LPC_REG_LED2_OFF, ++ .mask = MLXPLAT_CPLD_LED_HI_NIBBLE_MASK, ++ }, ++ { ++ .label = "fan2:orange", ++ .reg = MLXPLAT_CPLD_LPC_REG_LED2_OFF, ++ .mask = MLXPLAT_CPLD_LED_HI_NIBBLE_MASK, ++ }, ++ { ++ .label = "fan3:green", ++ .reg = MLXPLAT_CPLD_LPC_REG_LED3_OFF, ++ .mask = MLXPLAT_CPLD_LED_LO_NIBBLE_MASK, ++ }, ++ { ++ .label = "fan3:orange", ++ .reg = MLXPLAT_CPLD_LPC_REG_LED3_OFF, ++ .mask = MLXPLAT_CPLD_LED_LO_NIBBLE_MASK, ++ }, ++ { ++ .label = "fan4:green", ++ .reg = MLXPLAT_CPLD_LPC_REG_LED3_OFF, ++ .mask = MLXPLAT_CPLD_LED_HI_NIBBLE_MASK, ++ }, ++ { ++ .label = "fan4:orange", ++ .reg = MLXPLAT_CPLD_LPC_REG_LED3_OFF, ++ .mask = MLXPLAT_CPLD_LED_HI_NIBBLE_MASK, ++ }, ++ { ++ .label = "fan5:green", ++ .reg = MLXPLAT_CPLD_LPC_REG_LED4_OFF, ++ .mask = MLXPLAT_CPLD_LED_LO_NIBBLE_MASK, ++ }, ++ { ++ .label = "fan5:orange", ++ .reg = MLXPLAT_CPLD_LPC_REG_LED4_OFF, ++ .mask = MLXPLAT_CPLD_LED_LO_NIBBLE_MASK, ++ }, ++ { ++ .label = "fan6:green", ++ .reg = MLXPLAT_CPLD_LPC_REG_LED4_OFF, ++ .mask = MLXPLAT_CPLD_LED_HI_NIBBLE_MASK, ++ }, ++ { ++ .label = "fan6:orange", ++ .reg = MLXPLAT_CPLD_LPC_REG_LED4_OFF, ++ .mask = MLXPLAT_CPLD_LED_HI_NIBBLE_MASK, ++ }, ++}; ++ ++static struct mlxreg_core_led_platform_data mlxplat_default_ng_led_data = { ++ .data = mlxplat_mlxcpld_default_ng_led_data, ++ .counter = ARRAY_SIZE(mlxplat_mlxcpld_default_ng_led_data), ++}; ++ ++static int ++mlxplat_mlxcpld_reg_read(void *context, unsigned int reg, unsigned int *val) ++{ ++ *val = ioread8(context + reg); ++ return 0; ++} ++ ++static int ++mlxplat_mlxcpld_reg_write(void *context, unsigned int reg, unsigned int val) ++{ ++ iowrite8(val, context + reg); ++ return 0; ++} ++ ++const struct regmap_config mlxplat_mlxcpld_regmap_config = { ++ .reg_bits = 8, ++ .val_bits = 8, ++ .max_register = 255, ++ .reg_read = mlxplat_mlxcpld_reg_read, ++ .reg_write = mlxplat_mlxcpld_reg_write, ++}; ++ ++static struct resource mlxplat_mlxcpld_resources[] = { ++ [0] = DEFINE_RES_IRQ_NAMED(17, "mlxreg-hotplug"), ++}; ++ ++struct platform_device *mlxplat_dev; ++struct mlxreg_core_hotplug_platform_data *mlxplat_hotplug; ++struct mlxreg_core_led_platform_data *mlxplat_led; ++ ++static int __init mlxplat_dmi_default_matched(const struct dmi_system_id *dmi) ++{ ++ int i; ++ ++ for (i = 0; i < ARRAY_SIZE(mlxplat_mux_data); i++) { ++ mlxplat_mux_data[i].values = mlxplat_default_channels[i]; ++ mlxplat_mux_data[i].n_values = ++ ARRAY_SIZE(mlxplat_default_channels[i]); ++ } ++ mlxplat_hotplug = &mlxplat_mlxcpld_default_data; ++ mlxplat_led = &mlxplat_default_led_data; ++ ++ return 1; ++}; ++ ++static int __init mlxplat_dmi_msn21xx_matched(const struct dmi_system_id *dmi) ++{ ++ int i; ++ ++ for (i = 0; i < ARRAY_SIZE(mlxplat_mux_data); i++) { ++ mlxplat_mux_data[i].values = mlxplat_msn21xx_channels; ++ mlxplat_mux_data[i].n_values = ++ ARRAY_SIZE(mlxplat_msn21xx_channels); ++ } ++ mlxplat_hotplug = &mlxplat_mlxcpld_msn21xx_data; ++ mlxplat_led = &mlxplat_msn21xx_led_data; ++ ++ return 1; ++}; ++ ++static int __init mlxplat_dmi_msn274x_matched(const struct dmi_system_id *dmi) ++{ ++ int i; ++ ++ for (i = 0; i < ARRAY_SIZE(mlxplat_mux_data); i++) { ++ mlxplat_mux_data[i].values = mlxplat_msn21xx_channels; ++ mlxplat_mux_data[i].n_values = ++ ARRAY_SIZE(mlxplat_msn21xx_channels); ++ } ++ mlxplat_hotplug = &mlxplat_mlxcpld_msn274x_data; ++ mlxplat_led = &mlxplat_default_led_data; ++ ++ return 1; ++}; ++ ++static int __init mlxplat_dmi_qmb7xx_matched(const struct dmi_system_id *dmi) ++{ ++ int i; ++ ++ for (i = 0; i < ARRAY_SIZE(mlxplat_mux_data); i++) { ++ mlxplat_mux_data[i].values = mlxplat_msn21xx_channels; ++ mlxplat_mux_data[i].n_values = ++ ARRAY_SIZE(mlxplat_msn21xx_channels); ++ } ++ mlxplat_hotplug = &mlxplat_mlxcpld_default_ng_data; ++ mlxplat_led = &mlxplat_default_ng_led_data; ++ ++ return 1; ++}; ++ ++static int __init mlxplat_dmi_msn201x_matched(const struct dmi_system_id *dmi) ++{ ++ int i; ++ ++ for (i = 0; i < ARRAY_SIZE(mlxplat_mux_data); i++) { ++ mlxplat_mux_data[i].values = mlxplat_msn21xx_channels; ++ mlxplat_mux_data[i].n_values = ++ ARRAY_SIZE(mlxplat_msn21xx_channels); ++ } ++ mlxplat_hotplug = &mlxplat_mlxcpld_msn201x_data; ++ mlxplat_led = &mlxplat_msn21xx_led_data; ++ ++ return 1; ++}; ++ ++static struct dmi_system_id mlxplat_dmi_table[] __initdata = { ++ { ++ .callback = mlxplat_dmi_msn274x_matched, ++ .matches = { ++ DMI_MATCH(DMI_BOARD_VENDOR, "Mellanox Technologies"), ++ DMI_MATCH(DMI_PRODUCT_NAME, "MSN274"), ++ }, ++ }, ++ { ++ .callback = mlxplat_dmi_default_matched, ++ .matches = { ++ DMI_MATCH(DMI_BOARD_VENDOR, "Mellanox Technologies"), ++ DMI_MATCH(DMI_PRODUCT_NAME, "MSN24"), ++ }, ++ }, ++ { ++ .callback = mlxplat_dmi_default_matched, ++ .matches = { ++ DMI_MATCH(DMI_BOARD_VENDOR, "Mellanox Technologies"), ++ DMI_MATCH(DMI_PRODUCT_NAME, "MSN27"), ++ }, ++ }, ++ { ++ .callback = mlxplat_dmi_default_matched, ++ .matches = { ++ DMI_MATCH(DMI_BOARD_VENDOR, "Mellanox Technologies"), ++ DMI_MATCH(DMI_PRODUCT_NAME, "MSB"), ++ }, ++ }, ++ { ++ .callback = mlxplat_dmi_default_matched, ++ .matches = { ++ DMI_MATCH(DMI_BOARD_VENDOR, "Mellanox Technologies"), ++ DMI_MATCH(DMI_PRODUCT_NAME, "MSX"), ++ }, ++ }, ++ { ++ .callback = mlxplat_dmi_msn21xx_matched, ++ .matches = { ++ DMI_MATCH(DMI_BOARD_VENDOR, "Mellanox Technologies"), ++ DMI_MATCH(DMI_PRODUCT_NAME, "MSN21"), ++ }, ++ }, ++ { ++ .callback = mlxplat_dmi_msn201x_matched, ++ .matches = { ++ DMI_MATCH(DMI_BOARD_VENDOR, "Mellanox Technologies"), ++ DMI_MATCH(DMI_PRODUCT_NAME, "MSN201"), ++ }, ++ }, ++ { ++ .callback = mlxplat_dmi_qmb7xx_matched, ++ .matches = { ++ DMI_MATCH(DMI_BOARD_VENDOR, "Mellanox Technologies"), ++ DMI_MATCH(DMI_PRODUCT_NAME, "QMB7"), ++ }, ++ }, ++ { ++ .callback = mlxplat_dmi_qmb7xx_matched, ++ .matches = { ++ DMI_MATCH(DMI_BOARD_VENDOR, "Mellanox Technologies"), ++ DMI_MATCH(DMI_PRODUCT_NAME, "SN37"), ++ }, ++ }, ++ { ++ .callback = mlxplat_dmi_qmb7xx_matched, ++ .matches = { ++ DMI_MATCH(DMI_BOARD_VENDOR, "Mellanox Technologies"), ++ DMI_MATCH(DMI_PRODUCT_NAME, "SN34"), ++ }, ++ }, ++ { } ++}; ++ ++static int __init mlxplat_init(void) ++{ ++ struct mlxplat_priv *priv; ++ void __iomem *base; ++ int i, err = 0; ++ ++ if (!dmi_check_system(mlxplat_dmi_table)) ++ return -ENODEV; ++ ++ mlxplat_dev = platform_device_register_simple(MLX_PLAT_DEVICE_NAME, -1, ++ mlxplat_lpc_resources, ++ ARRAY_SIZE(mlxplat_lpc_resources)); ++ ++ if (IS_ERR(mlxplat_dev)) ++ return PTR_ERR(mlxplat_dev); ++ ++ priv = devm_kzalloc(&mlxplat_dev->dev, sizeof(struct mlxplat_priv), ++ GFP_KERNEL); ++ if (!priv) { ++ err = -ENOMEM; ++ goto fail_alloc; ++ } ++ platform_set_drvdata(mlxplat_dev, priv); ++ ++ priv->pdev_i2c = platform_device_register_simple("i2c_mlxcpld", -1, ++ NULL, 0); ++ if (IS_ERR(priv->pdev_i2c)) { ++ err = PTR_ERR(priv->pdev_i2c); ++ goto fail_alloc; ++ } ++ ++ for (i = 0; i < ARRAY_SIZE(mlxplat_mux_data); i++) { ++ priv->pdev_mux[i] = platform_device_register_resndata( ++ &mlxplat_dev->dev, ++ "i2c-mux-reg", i, NULL, ++ 0, &mlxplat_mux_data[i], ++ sizeof(mlxplat_mux_data[i])); ++ if (IS_ERR(priv->pdev_mux[i])) { ++ err = PTR_ERR(priv->pdev_mux[i]); ++ goto fail_platform_mux_register; ++ } ++ } ++ ++ base = devm_ioport_map(&mlxplat_dev->dev, ++ mlxplat_lpc_resources[1].start, 1); ++ if (IS_ERR(base)) ++ goto fail_platform_mux_register; ++ ++ mlxplat_hotplug->regmap = devm_regmap_init(&mlxplat_dev->dev, NULL, ++ base, &mlxplat_mlxcpld_regmap_config); ++ if (IS_ERR(mlxplat_hotplug->regmap)) ++ goto fail_platform_mux_register; ++ ++ priv->pdev_hotplug = platform_device_register_resndata( ++ &mlxplat_dev->dev, "mlxreg-hotplug", ++ PLATFORM_DEVID_NONE, ++ mlxplat_mlxcpld_resources, ++ ARRAY_SIZE(mlxplat_mlxcpld_resources), ++ mlxplat_hotplug, sizeof(*mlxplat_hotplug)); ++ if (IS_ERR(priv->pdev_hotplug)) { ++ err = PTR_ERR(priv->pdev_hotplug); ++ goto fail_platform_mux_register; ++ } ++ ++ mlxplat_led->regmap = mlxplat_hotplug->regmap; ++ priv->pdev_led = platform_device_register_resndata( ++ &mlxplat_dev->dev, "leds-mlxreg", ++ PLATFORM_DEVID_NONE, NULL, 0, ++ mlxplat_led, sizeof(*mlxplat_led)); ++ if (IS_ERR(priv->pdev_led)) { ++ err = PTR_ERR(priv->pdev_led); ++ goto fail_platform_hotplug_register; ++ } ++ ++ return 0; ++ ++fail_platform_hotplug_register: ++ platform_device_unregister(priv->pdev_hotplug); ++fail_platform_mux_register: ++ while (--i >= 0) ++ platform_device_unregister(priv->pdev_mux[i]); ++ platform_device_unregister(priv->pdev_i2c); ++fail_alloc: ++ platform_device_unregister(mlxplat_dev); ++ ++ return err; ++} ++module_init(mlxplat_init); ++ ++static void __exit mlxplat_exit(void) ++{ ++ struct mlxplat_priv *priv = platform_get_drvdata(mlxplat_dev); ++ int i; ++ ++ platform_device_unregister(priv->pdev_led); ++ platform_device_unregister(priv->pdev_hotplug); ++ ++ for (i = ARRAY_SIZE(mlxplat_mux_data) - 1; i >= 0 ; i--) ++ platform_device_unregister(priv->pdev_mux[i]); ++ ++ platform_device_unregister(priv->pdev_i2c); ++ platform_device_unregister(mlxplat_dev); ++} ++module_exit(mlxplat_exit); ++ ++MODULE_AUTHOR("Vadim Pasternak (vadimp@mellanox.com)"); ++MODULE_DESCRIPTION("Mellanox platform driver"); ++MODULE_LICENSE("Dual BSD/GPL"); ++MODULE_ALIAS("dmi:*:*Mellanox*:MSN24*:"); ++MODULE_ALIAS("dmi:*:*Mellanox*:MSN27*:"); ++MODULE_ALIAS("dmi:*:*Mellanox*:MSB*:"); ++MODULE_ALIAS("dmi:*:*Mellanox*:MSX*:"); ++MODULE_ALIAS("dmi:*:*Mellanox*:MSN21*:"); ++MODULE_ALIAS("dmi:*:*Mellanox*MSN274*:"); ++MODULE_ALIAS("dmi:*:*Mellanox*MSN201*:"); ++MODULE_ALIAS("dmi:*:*Mellanox*QMB7*:"); ++MODULE_ALIAS("dmi:*:*Mellanox*SN37*:"); ++MODULE_ALIAS("dmi:*:*Mellanox*QM34*:"); diff --git a/patch/0004-platform-x86-Introduce-support-for-Mellanox-hotplug-.patch b/patch/0004-platform-x86-Introduce-support-for-Mellanox-hotplug-.patch new file mode 100644 index 000000000..58973010c --- /dev/null +++ b/patch/0004-platform-x86-Introduce-support-for-Mellanox-hotplug-.patch @@ -0,0 +1,905 @@ +Linux backport patch. Includes following commits: +2926024b5081fc8d4b086677bafa1ac55ea0b911 +6124fdf76488681713f278f3fdf2ba2dfe760211 +c84002d15210ca130263e23911cc399202124eb4 +07b89c2b2a5e8ce30166b96f87b324c6b419f108 +91973760712f350048a0fa8e0363e260bf874313 +c2e714e56360e34f88e0a75ee74e467d8b82de75 +af4779be0f2cec63f4cb15d3db78c5de3523756a +d53bc5dc941653f0ed93b11a647bd6ff40f40ef2 + + +diff -Nur a/drivers/platform/mellanox/Kconfig b/drivers/platform/mellanox/Kconfig +--- a/drivers/platform/mellanox/Kconfig 1970-01-01 00:00:00.000000000 +0000 ++++ b/drivers/platform/mellanox/Kconfig 2017-11-12 08:54:58.200076777 +0000 +@@ -0,0 +1,25 @@ ++# ++# Platform support for Mellanox hardware ++# ++ ++menuconfig MELLANOX_PLATFORM ++ bool "Platform support for Mellanox hardware" ++ depends on X86 || ARM || COMPILE_TEST ++ ---help--- ++ Say Y here to get to see options for platform support for ++ Mellanox systems. This option alone does not add any kernel code. ++ ++ If you say N, all options in this submenu will be skipped and disabled. ++ ++if MELLANOX_PLATFORM ++ ++config MLXREG_HOTPLUG ++ tristate "Mellanox platform hotplug driver support" ++ depends on REGMAP ++ depends on HWMON ++ depends on I2C ++ ---help--- ++ This driver handles hot-plug events for the power suppliers, power ++ cables and fans on the wide range Mellanox IB and Ethernet systems. ++ ++endif # MELLANOX_PLATFORM +diff -Nur a/drivers/platform/mellanox/Makefile b/drivers/platform/mellanox/Makefile +--- a/drivers/platform/mellanox/Makefile 1970-01-01 00:00:00.000000000 +0000 ++++ b/drivers/platform/mellanox/Makefile 2017-11-12 08:54:58.200076777 +0000 +@@ -0,0 +1 @@ ++obj-$(CONFIG_MLXREG_HOTPLUG) += mlxreg-hotplug.o +diff -Nur a/drivers/platform/mellanox/mlxreg-hotplug.c b/drivers/platform/mellanox/mlxreg-hotplug.c +--- a/drivers/platform/mellanox/mlxreg-hotplug.c 1970-01-01 00:00:00.000000000 +0000 ++++ b/drivers/platform/mellanox/mlxreg-hotplug.c 2017-11-12 08:54:58.200076777 +0000 +@@ -0,0 +1,710 @@ ++/* ++ * Copyright (c) 2017 Mellanox Technologies. All rights reserved. ++ * Copyright (c) 2017 Vadim Pasternak ++ * ++ * Redistribution and use in source and binary forms, with or without ++ * modification, are permitted provided that the following conditions are met: ++ * ++ * 1. Redistributions of source code must retain the above copyright ++ * notice, this list of conditions and the following disclaimer. ++ * 2. Redistributions in binary form must reproduce the above copyright ++ * notice, this list of conditions and the following disclaimer in the ++ * documentation and/or other materials provided with the distribution. ++ * 3. Neither the names of the copyright holders nor the names of its ++ * contributors may be used to endorse or promote products derived from ++ * this software without specific prior written permission. ++ * ++ * Alternatively, this software may be distributed under the terms of the ++ * GNU General Public License ("GPL") version 2 as published by the Free ++ * Software Foundation. ++ * ++ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" ++ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE ++ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ++ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE ++ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR ++ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF ++ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS ++ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN ++ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ++ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE ++ * POSSIBILITY OF SUCH DAMAGE. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++/* Offset of event and mask registers from status register. */ ++#define MLXREG_HOTPLUG_EVENT_OFF 1 ++#define MLXREG_HOTPLUG_MASK_OFF 2 ++#define MLXREG_HOTPLUG_AGGR_MASK_OFF 1 ++ ++/* ASIC health parameters. */ ++#define MLXREG_HOTPLUG_HEALTH_MASK 0x02 ++#define MLXREG_HOTPLUG_RST_CNTR 3 ++ ++#define MLXREG_HOTPLUG_PROP_OKAY "okay" ++#define MLXREG_HOTPLUG_PROP_DISABLED "disabled" ++#define MLXREG_HOTPLUG_PROP_STATUS "status" ++ ++#define MLXREG_HOTPLUG_ATTRS_MAX 24 ++ ++/** ++ * struct mlxreg_hotplug_priv_data - platform private data: ++ * @irq: platform device interrupt number; ++ * @pdev: platform device; ++ * @plat: platform data; ++ * @dwork: delayed work template; ++ * @lock: spin lock; ++ * @hwmon: hwmon device; ++ * @mlxreg_hotplug_attr: sysfs attributes array; ++ * @mlxreg_hotplug_dev_attr: sysfs sensor device attribute array; ++ * @group: sysfs attribute group; ++ * @groups: list of sysfs attribute group for hwmon registration; ++ * @cell: location of top aggregation interrupt register; ++ * @mask: top aggregation interrupt common mask; ++ * @aggr_cache: last value of aggregation register status; ++ */ ++struct mlxreg_hotplug_priv_data { ++ int irq; ++ struct device *dev; ++ struct platform_device *pdev; ++ struct mlxreg_hotplug_platform_data *plat; ++ struct regmap *regmap; ++ struct delayed_work dwork_irq; ++ struct delayed_work dwork; ++ spinlock_t lock; /* sync with interrupt */ ++ struct device *hwmon; ++ struct attribute *mlxreg_hotplug_attr[MLXREG_HOTPLUG_ATTRS_MAX + 1]; ++ struct sensor_device_attribute_2 ++ mlxreg_hotplug_dev_attr[MLXREG_HOTPLUG_ATTRS_MAX]; ++ struct attribute_group group; ++ const struct attribute_group *groups[2]; ++ u32 cell; ++ u32 mask; ++ u32 aggr_cache; ++ bool after_probe; ++}; ++ ++#if defined(CONFIG_OF_DYNAMIC) ++/** ++ * struct mlxreg_hotplug_device_en - Open Firmware property for enabling device ++ * ++ * @name - property name; ++ * @value - property value string; ++ * @length - length of proprty value string; ++ * ++ * The structure is used for the devices, which require some dynamic ++ * selection operation allowing access to them. ++ */ ++static struct property mlxreg_hotplug_device_en = { ++ .name = MLXREG_HOTPLUG_PROP_STATUS, ++ .value = MLXREG_HOTPLUG_PROP_OKAY, ++ .length = sizeof(MLXREG_HOTPLUG_PROP_OKAY), ++}; ++ ++/** ++ * struct mlxreg_hotplug_device_dis - Open Firmware property for disabling ++ * device ++ * ++ * @name - property name; ++ * @value - property value string; ++ * @length - length of proprty value string; ++ * ++ * The structure is used for the devices, which require some dynamic ++ * selection operation disallowing access to them. ++ */ ++static struct property mlxreg_hotplug_device_dis = { ++ .name = MLXREG_HOTPLUG_PROP_STATUS, ++ .value = MLXREG_HOTPLUG_PROP_DISABLED, ++ .length = sizeof(MLXREG_HOTPLUG_PROP_DISABLED), ++}; ++ ++static int mlxreg_hotplug_of_device_create(struct mlxreg_core_data *data) ++{ ++ return of_update_property(data->np, &mlxreg_hotplug_device_en); ++} ++ ++static void mlxreg_hotplug_of_device_destroy(struct mlxreg_core_data *data) ++{ ++ of_update_property(data->np, &mlxreg_hotplug_device_dis); ++ of_node_clear_flag(data->np, OF_POPULATED); ++} ++#else ++static int mlxreg_hotplug_of_device_create(struct mlxreg_core_data *data) ++{ ++ return 0; ++} ++ ++static void mlxreg_hotplug_of_device_destroy(struct mlxreg_core_data *data) ++{ ++} ++#endif ++ ++static int mlxreg_hotplug_device_create(struct mlxreg_core_data *data) ++{ ++ data->hpdev.adapter = i2c_get_adapter(data->hpdev.nr); ++ if (!data->hpdev.adapter) ++ return -EFAULT; ++ ++ data->hpdev.client = i2c_new_device(data->hpdev.adapter, ++ data->hpdev.brdinfo); ++ if (!data->hpdev.client) { ++ i2c_put_adapter(data->hpdev.adapter); ++ data->hpdev.adapter = NULL; ++ return -EFAULT; ++ } ++ ++ return 0; ++} ++ ++static void mlxreg_hotplug_device_destroy(struct mlxreg_core_data *data) ++{ ++ if (data->hpdev.client) { ++ i2c_unregister_device(data->hpdev.client); ++ data->hpdev.client = NULL; ++ } ++ ++ if (data->hpdev.adapter) { ++ i2c_put_adapter(data->hpdev.adapter); ++ data->hpdev.adapter = NULL; ++ } ++} ++ ++static int mlxreg_hotplug_dev_enable(struct mlxreg_core_data *data) ++{ ++ int err; ++ ++ /* Enable and create device. */ ++ if (data->np) ++ err = mlxreg_hotplug_of_device_create(data); ++ else ++ err = mlxreg_hotplug_device_create(data); ++ ++ return err; ++} ++ ++static void mlxreg_hotplug_dev_disable(struct mlxreg_core_data *data) ++{ ++ /* Disable and unregister platform device. */ ++ if (data->np) ++ mlxreg_hotplug_of_device_destroy(data); ++ else ++ mlxreg_hotplug_device_destroy(data); ++} ++ ++static ssize_t mlxreg_hotplug_attr_show(struct device *dev, ++ struct device_attribute *attr, ++ char *buf) ++{ ++ struct mlxreg_hotplug_priv_data *priv = dev_get_drvdata(dev); ++ struct mlxreg_core_hotplug_platform_data *pdata; ++ int index = to_sensor_dev_attr_2(attr)->index; ++ int nr = to_sensor_dev_attr_2(attr)->nr; ++ struct mlxreg_core_item *item; ++ struct mlxreg_core_data *data; ++ u32 regval; ++ int ret; ++ ++ pdata = dev_get_platdata(&priv->pdev->dev); ++ item = pdata->items + nr; ++ data = item->data + index; ++ ++ ret = regmap_read(priv->regmap, data->reg, ®val); ++ if (ret) ++ return ret; ++ ++ if (item->health) { ++ regval &= data->mask; ++ } else { ++ /* Bit = 0 : functional if item->inversed is true. */ ++ if (item->inversed) ++ regval = !(regval & data->mask); ++ else ++ regval = !!(regval & data->mask); ++ } ++ ++ return sprintf(buf, "%u\n", regval); ++} ++ ++#define PRIV_ATTR(i) priv->mlxreg_hotplug_attr[i] ++#define PRIV_DEV_ATTR(i) priv->mlxreg_hotplug_dev_attr[i] ++ ++static int mlxreg_hotplug_attr_init(struct mlxreg_hotplug_priv_data *priv) ++{ ++ struct mlxreg_core_hotplug_platform_data *pdata; ++ struct mlxreg_core_item *item; ++ struct mlxreg_core_data *data; ++ int num_attrs = 0, id = 0, i, j; ++ ++ pdata = dev_get_platdata(&priv->pdev->dev); ++ item = pdata->items; ++ ++ /* Go over all kinds of items - psu, pwr, fan. */ ++ for (i = 0; i < pdata->counter; i++, item++) { ++ num_attrs += item->count; ++ data = item->data; ++ /* Go over all units within the item. */ ++ for (j = 0; j < item->count; j++, data++, id++) { ++ PRIV_ATTR(id) = &PRIV_DEV_ATTR(id).dev_attr.attr; ++ PRIV_ATTR(id)->name = devm_kasprintf(&priv->pdev->dev, ++ GFP_KERNEL, ++ data->label); ++ ++ if (!PRIV_ATTR(id)->name) { ++ dev_err(priv->dev, "Memory allocation failed for attr %d.\n", ++ id); ++ return -ENOMEM; ++ } ++ ++ PRIV_DEV_ATTR(id).dev_attr.attr.name = ++ PRIV_ATTR(id)->name; ++ PRIV_DEV_ATTR(id).dev_attr.attr.mode = 0444; ++ PRIV_DEV_ATTR(id).dev_attr.show = ++ mlxreg_hotplug_attr_show; ++ PRIV_DEV_ATTR(id).nr = i; ++ PRIV_DEV_ATTR(id).index = j; ++ sysfs_attr_init(&PRIV_DEV_ATTR(id).dev_attr.attr); ++ } ++ } ++ ++ priv->group.attrs = devm_kzalloc(&priv->pdev->dev, num_attrs * ++ sizeof(struct attribute *), ++ GFP_KERNEL); ++ if (!priv->group.attrs) ++ return -ENOMEM; ++ ++ priv->group.attrs = priv->mlxreg_hotplug_attr; ++ priv->groups[0] = &priv->group; ++ priv->groups[1] = NULL; ++ ++ return 0; ++} ++ ++static void ++mlxreg_hotplug_work_helper(struct mlxreg_hotplug_priv_data *priv, ++ struct mlxreg_core_item *item) ++{ ++ struct mlxreg_core_data *data; ++ u32 asserted, regval, bit; ++ int ret; ++ ++ /* ++ * Validate if item related to received signal type is valid. ++ * It should never happen, excepted the situation when some ++ * piece of hardware is broken. In such situation just produce ++ * error message and return. Caller must continue to handle the ++ * signals from other devices if any. ++ */ ++ if (unlikely(!item)) { ++ dev_err(priv->dev, "False signal: at offset:mask 0x%02x:0x%02x.\n", ++ item->reg, item->mask); ++ ++ return; ++ } ++ ++ /* Mask event. */ ++ ret = regmap_write(priv->regmap, item->reg + MLXREG_HOTPLUG_MASK_OFF, ++ 0); ++ if (ret) ++ goto access_error; ++ ++ /* Read status. */ ++ ret = regmap_read(priv->regmap, item->reg, ®val); ++ if (ret) ++ goto access_error; ++ ++ /* Set asserted bits and save last status. */ ++ regval &= item->mask; ++ asserted = item->cache ^ regval; ++ item->cache = regval; ++ ++ for_each_set_bit(bit, (unsigned long *)&asserted, 8) { ++ data = item->data + bit; ++ if (regval & BIT(bit)) { ++ if (item->inversed) ++ mlxreg_hotplug_dev_disable(data); ++ else ++ mlxreg_hotplug_dev_enable(data); ++ } else { ++ if (item->inversed) ++ mlxreg_hotplug_dev_enable(data); ++ else ++ mlxreg_hotplug_dev_disable(data); ++ } ++ } ++ ++ /* Acknowledge event. */ ++ ret = regmap_write(priv->regmap, item->reg + MLXREG_HOTPLUG_EVENT_OFF, ++ 0); ++ if (ret) ++ goto access_error; ++ ++ /* Unmask event. */ ++ ret = regmap_write(priv->regmap, item->reg + MLXREG_HOTPLUG_MASK_OFF, ++ item->mask); ++ if (ret) ++ goto access_error; ++ ++ return; ++ ++access_error: ++ dev_err(priv->dev, "Failed to complete workqueue.\n"); ++} ++ ++static void ++mlxreg_hotplug_health_work_helper(struct mlxreg_hotplug_priv_data *priv, ++ struct mlxreg_core_item *item) ++{ ++ struct mlxreg_core_data *data = item->data; ++ u32 regval; ++ int i, ret; ++ ++ for (i = 0; i < item->count; i++, data++) { ++ /* Mask event. */ ++ ret = regmap_write(priv->regmap, data->reg + ++ MLXREG_HOTPLUG_MASK_OFF, 0); ++ if (ret) ++ goto access_error; ++ ++ /* Read status. */ ++ ret = regmap_read(priv->regmap, data->reg, ®val); ++ if (ret) ++ goto access_error; ++ ++ regval &= data->mask; ++ item->cache = regval; ++ if (regval == MLXREG_HOTPLUG_HEALTH_MASK) { ++ if ((data->health_cntr++ == MLXREG_HOTPLUG_RST_CNTR) || ++ !priv->after_probe) { ++ mlxreg_hotplug_dev_enable(data); ++ data->attached = true; ++ } ++ } else { ++ if (data->attached) { ++ mlxreg_hotplug_dev_disable(data); ++ data->attached = false; ++ data->health_cntr = 0; ++ } ++ } ++ ++ /* Acknowledge event. */ ++ ret = regmap_write(priv->regmap, data->reg + ++ MLXREG_HOTPLUG_EVENT_OFF, 0); ++ if (ret) ++ goto access_error; ++ ++ /* Unmask event. */ ++ ret = regmap_write(priv->regmap, data->reg + ++ MLXREG_HOTPLUG_MASK_OFF, data->mask); ++ if (ret) ++ goto access_error; ++ } ++ ++ return; ++ ++access_error: ++ dev_err(priv->dev, "Failed to complete workqueue.\n"); ++} ++ ++/* ++ * mlxreg_hotplug_work_handler - performs traversing of device interrupt ++ * registers according to the below hierarchy schema: ++ * ++ * Aggregation registers (status/mask) ++ * PSU registers: *---* ++ * *-----------------* | | ++ * |status/event/mask|-----> | * | ++ * *-----------------* | | ++ * Power registers: | | ++ * *-----------------* | | ++ * |status/event/mask|-----> | * | ++ * *-----------------* | | ++ * FAN registers: | |--> CPU ++ * *-----------------* | | ++ * |status/event/mask|-----> | * | ++ * *-----------------* | | ++ * ASIC registers: | | ++ * *-----------------* | | ++ * |status/event/mask|-----> | * | ++ * *-----------------* | | ++ * *---* ++ * ++ * In case some system changed are detected: FAN in/out, PSU in/out, power ++ * cable attached/detached, ASIC helath good/bad, relevant device is created ++ * or destroyed. ++ */ ++static void mlxreg_hotplug_work_handler(struct work_struct *work) ++{ ++ struct mlxreg_hotplug_priv_data *priv = container_of(work, ++ struct mlxreg_hotplug_priv_data, dwork_irq.work); ++ struct mlxreg_core_hotplug_platform_data *pdata; ++ struct mlxreg_core_item *item; ++ unsigned long flags; ++ u32 regval, aggr_asserted; ++ int i; ++ int ret; ++ ++ pdata = dev_get_platdata(&priv->pdev->dev); ++ item = pdata->items; ++ /* Mask aggregation event. */ ++ ret = regmap_write(priv->regmap, pdata->cell + ++ MLXREG_HOTPLUG_AGGR_MASK_OFF, 0); ++ if (ret < 0) ++ goto access_error; ++ ++ /* Read aggregation status. */ ++ ret = regmap_read(priv->regmap, pdata->cell, ®val); ++ if (ret) ++ goto access_error; ++ ++ regval &= pdata->mask; ++ aggr_asserted = priv->aggr_cache ^ regval; ++ priv->aggr_cache = regval; ++ ++ /* Handle topology and health configuration changes. */ ++ for (i = 0; i < pdata->counter; i++, item++) { ++ if (aggr_asserted & item->aggr_mask) { ++ if (item->health) ++ mlxreg_hotplug_health_work_helper(priv, item); ++ else ++ mlxreg_hotplug_work_helper(priv, item); ++ } ++ } ++ ++ if (aggr_asserted) { ++ spin_lock_irqsave(&priv->lock, flags); ++ ++ /* ++ * It is possible, that some signals have been inserted, while ++ * interrupt has been masked by mlxreg_hotplug_work_handler. ++ * In this case such signals will be missed. In order to handle ++ * these signals delayed work is canceled and work task ++ * re-scheduled for immediate execution. It allows to handle ++ * missed signals, if any. In other case work handler just ++ * validates that no new signals have been received during ++ * masking. ++ */ ++ cancel_delayed_work(&priv->dwork_irq); ++ schedule_delayed_work(&priv->dwork_irq, 0); ++ ++ spin_unlock_irqrestore(&priv->lock, flags); ++ ++ return; ++ } ++ ++ /* Unmask aggregation event (no need acknowledge). */ ++ ret = regmap_write(priv->regmap, pdata->cell + ++ MLXREG_HOTPLUG_AGGR_MASK_OFF, pdata->mask); ++ if (ret) ++ goto access_error; ++ ++ return; ++ ++access_error: ++ dev_err(priv->dev, "Failed to complete workqueue.\n"); ++} ++ ++static int mlxreg_hotplug_set_irq(struct mlxreg_hotplug_priv_data *priv) ++{ ++ struct mlxreg_core_hotplug_platform_data *pdata; ++ struct mlxreg_core_item *item; ++ int i; ++ int ret; ++ ++ pdata = dev_get_platdata(&priv->pdev->dev); ++ item = pdata->items; ++ ++ for (i = 0; i < pdata->counter; i++, item++) { ++ /* Clear group presense event. */ ++ ret = regmap_write(priv->regmap, item->reg + ++ MLXREG_HOTPLUG_EVENT_OFF, 0); ++ if (ret) ++ goto access_error; ++ ++ /* Set group initial status as mask and unmask group event. */ ++ if (item->inversed) { ++ item->cache = item->mask; ++ ret = regmap_write(priv->regmap, item->reg + ++ MLXREG_HOTPLUG_MASK_OFF, ++ item->mask); ++ if (ret) ++ goto access_error; ++ } ++ } ++ ++ /* Keep aggregation initial status as zero and unmask events. */ ++ ret = regmap_write(priv->regmap, pdata->cell + ++ MLXREG_HOTPLUG_AGGR_MASK_OFF, pdata->mask); ++ if (ret) ++ goto access_error; ++ ++ /* Keep low aggregation initial status as zero and unmask events. */ ++ ret = regmap_write(priv->regmap, pdata->cell_low + ++ MLXREG_HOTPLUG_AGGR_MASK_OFF, pdata->mask_low); ++ if (ret) ++ goto access_error; ++ ++ /* Invoke work handler for initializing hot plug devices setting. */ ++ mlxreg_hotplug_work_handler(&priv->dwork_irq.work); ++ ++ enable_irq(priv->irq); ++ ++ return 0; ++ ++access_error: ++ dev_err(priv->dev, "Failed to set interrupts.\n"); ++ ++ enable_irq(priv->irq); ++ ++ return ret; ++} ++ ++static void mlxreg_hotplug_unset_irq(struct mlxreg_hotplug_priv_data *priv) ++{ ++ struct mlxreg_core_hotplug_platform_data *pdata; ++ struct mlxreg_core_item *item; ++ struct mlxreg_core_data *data; ++ int count, i, j; ++ ++ pdata = dev_get_platdata(&priv->pdev->dev); ++ item = pdata->items; ++ disable_irq(priv->irq); ++ cancel_delayed_work_sync(&priv->dwork_irq); ++ ++ /* Mask low aggregation event. */ ++ regmap_write(priv->regmap, pdata->cell_low + ++ MLXREG_HOTPLUG_AGGR_MASK_OFF, 0); ++ ++ /* Mask aggregation event. */ ++ regmap_write(priv->regmap, pdata->cell + MLXREG_HOTPLUG_AGGR_MASK_OFF, ++ 0); ++ ++ /* Clear topology configurations. */ ++ for (i = 0; i < pdata->counter; i++, item++) { ++ data = item->data; ++ /* Mask group presense event. */ ++ regmap_write(priv->regmap, data->reg + MLXREG_HOTPLUG_MASK_OFF, ++ 0); ++ /* Clear group presense event. */ ++ regmap_write(priv->regmap, data->reg + ++ MLXREG_HOTPLUG_EVENT_OFF, 0); ++ ++ /* Remove all the attached devices in group. */ ++ count = item->count; ++ for (j = 0; j < count; j++, data++) ++ mlxreg_hotplug_dev_disable(data); ++ } ++} ++ ++static irqreturn_t mlxreg_hotplug_irq_handler(int irq, void *dev) ++{ ++ struct mlxreg_hotplug_priv_data *priv = ++ (struct mlxreg_hotplug_priv_data *)dev; ++ ++ /* Schedule work task for immediate execution.*/ ++ schedule_delayed_work(&priv->dwork_irq, 0); ++ ++ return IRQ_HANDLED; ++} ++ ++static int mlxreg_hotplug_probe(struct platform_device *pdev) ++{ ++ struct mlxreg_core_hotplug_platform_data *pdata; ++ struct mlxreg_hotplug_priv_data *priv; ++ int err; ++ ++ pdata = dev_get_platdata(&pdev->dev); ++ if (!pdata) { ++ dev_err(&pdev->dev, "Failed to get platform data.\n"); ++ return -EINVAL; ++ } ++ ++ priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); ++ if (!priv) ++ return -ENOMEM; ++ ++ if (pdata->irq) { ++ priv->irq = pdata->irq; ++ } else { ++ priv->irq = platform_get_irq(pdev, 0); ++ if (priv->irq < 0) { ++ dev_err(&pdev->dev, "Failed to get platform irq: %d\n", ++ priv->irq); ++ return priv->irq; ++ } ++ } ++ ++ priv->regmap = pdata->regmap; ++ priv->dev = pdev->dev.parent; ++ priv->pdev = pdev; ++ ++ err = devm_request_irq(&pdev->dev, priv->irq, ++ mlxreg_hotplug_irq_handler, IRQF_TRIGGER_FALLING ++ | IRQF_SHARED, "mlxreg-hotplug", priv); ++ if (err) { ++ dev_err(&pdev->dev, "Failed to request irq: %d\n", err); ++ return err; ++ } ++ ++ disable_irq(priv->irq); ++ spin_lock_init(&priv->lock); ++ INIT_DELAYED_WORK(&priv->dwork_irq, mlxreg_hotplug_work_handler); ++ /* Perform initial interrupts setup. */ ++ mlxreg_hotplug_set_irq(priv); ++ ++ priv->after_probe = true; ++ dev_set_drvdata(&pdev->dev, priv); ++ ++ err = mlxreg_hotplug_attr_init(priv); ++ if (err) { ++ dev_err(&pdev->dev, "Failed to allocate attributes: %d\n", ++ err); ++ return err; ++ } ++ ++ priv->hwmon = devm_hwmon_device_register_with_groups(&pdev->dev, ++ "mlxreg_hotplug", priv, priv->groups); ++ if (IS_ERR(priv->hwmon)) { ++ dev_err(&pdev->dev, "Failed to register hwmon device %ld\n", ++ PTR_ERR(priv->hwmon)); ++ return PTR_ERR(priv->hwmon); ++ } ++ ++ return 0; ++} ++ ++static int mlxreg_hotplug_remove(struct platform_device *pdev) ++{ ++ struct mlxreg_hotplug_priv_data *priv = dev_get_drvdata(&pdev->dev); ++ ++ /* Clean interrupts setup. */ ++ mlxreg_hotplug_unset_irq(priv); ++ ++ return 0; ++} ++ ++static struct platform_driver mlxreg_hotplug_driver = { ++ .driver = { ++ .name = "mlxreg-hotplug", ++ }, ++ .probe = mlxreg_hotplug_probe, ++ .remove = mlxreg_hotplug_remove, ++}; ++ ++module_platform_driver(mlxreg_hotplug_driver); ++ ++MODULE_AUTHOR("Vadim Pasternak "); ++MODULE_DESCRIPTION("Mellanox regmap hotplug platform driver"); ++MODULE_LICENSE("Dual BSD/GPL"); ++MODULE_ALIAS("platform:mlxreg-hotplug"); +diff -Nur a/include/linux/platform_data/mlxreg.h b/include/linux/platform_data/mlxreg.h +--- a/include/linux/platform_data/mlxreg.h 1970-01-01 00:00:00.000000000 +0000 ++++ b/include/linux/platform_data/mlxreg.h 2017-11-12 09:04:09.796084101 +0000 +@@ -0,0 +1,142 @@ ++/* ++ * Copyright (c) 2017 Mellanox Technologies. All rights reserved. ++ * Copyright (c) 2017 Vadim Pasternak ++ * ++ * Redistribution and use in source and binary forms, with or without ++ * modification, are permitted provided that the following conditions are met: ++ * ++ * 1. Redistributions of source code must retain the above copyright ++ * notice, this list of conditions and the following disclaimer. ++ * 2. Redistributions in binary form must reproduce the above copyright ++ * notice, this list of conditions and the following disclaimer in the ++ * documentation and/or other materials provided with the distribution. ++ * 3. Neither the names of the copyright holders nor the names of its ++ * contributors may be used to endorse or promote products derived from ++ * this software without specific prior written permission. ++ * ++ * Alternatively, this software may be distributed under the terms of the ++ * GNU General Public License ("GPL") version 2 as published by the Free ++ * Software Foundation. ++ * ++ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" ++ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE ++ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ++ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE ++ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR ++ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF ++ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS ++ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN ++ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ++ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE ++ * POSSIBILITY OF SUCH DAMAGE. ++ */ ++ ++#ifndef __LINUX_PLATFORM_DATA_MLXREG_H ++#define __LINUX_PLATFORM_DATA_MLXREG_H ++ ++#define MLXREG_CORE_LABEL_MAX_SIZE 32 ++ ++/** ++ * struct mlxreg_hotplug_device - I2C device data: ++ * ++ * @adapter: I2C device adapter; ++ * @client: I2C device client; ++ * @brdinfo: device board information; ++ * @nr: I2C device adapter number, to which device is to be attached; ++ * ++ * Structure represents I2C hotplug device static data (board topology) and ++ * dynamic data (related kernel objects handles). ++ */ ++struct mlxreg_hotplug_device { ++ struct i2c_adapter *adapter; ++ struct i2c_client *client; ++ struct i2c_board_info *brdinfo; ++ int nr; ++}; ++ ++/** ++ * struct mlxreg_core_data - attributes control data: ++ * ++ * @label: attribute label; ++ * @label: attribute register offset; ++ * @reg: attribute register; ++ * @mask: attribute access mask; ++ * @bit: attribute effective bit; ++ * @np - pointer to node platform associated with attribute; ++ * @hpdev - hotplug device data; ++ * @health_cntr: dynamic device health indication counter; ++ * @attached: true if device has been attached after good helath indication; ++ */ ++struct mlxreg_core_data { ++ char label[MLXREG_CORE_LABEL_MAX_SIZE]; ++ u32 reg; ++ u32 mask; ++ u32 bit; ++ struct device_node *np; ++ struct mlxreg_hotplug_device hpdev; ++ u8 health_cntr; ++ bool attached; ++}; ++ ++/** ++ * struct mlxreg_core_item - same type components controlled by the driver: ++ * ++ * @data: component data; ++ * @aggr_mask: group aggregation mask; ++ * @reg: group interrupt status register; ++ * @mask: group interrupt mask; ++ * @cache: last status value for elements fro the same group; ++ * @count: number of available elements in the group; ++ * @ind: element's index inside the group; ++ * @inversed: if 0: 0 for signal status is OK, if 1 - 1 is OK; ++ * @health: true if device has health indication, false in other case; ++ */ ++struct mlxreg_core_item { ++ struct mlxreg_core_data *data; ++ u32 aggr_mask; ++ u32 reg; ++ u32 mask; ++ u32 cache; ++ u8 count; ++ u8 ind; ++ u8 inversed; ++ u8 health; ++}; ++ ++/** ++ * struct mlxreg_core_led_platform_data - led platform data: ++ * ++ * @led_data: led private data; ++ * @regmap: register map of parent device; ++ * @counter: number of led instances; ++ */ ++struct mlxreg_core_led_platform_data { ++ struct mlxreg_core_data *data; ++ void *regmap; ++ int counter; ++}; ++ ++/** ++ * struct mlxreg_core_hotplug_platform_data - hotplug platform data: ++ * ++ * @items: same type components with the hotplug capability; ++ * @irq: platform interrupt number; ++ * @regmap: register map of parent device; ++ * @counter: number of the components with the hotplug capability; ++ * @cell: location of top aggregation interrupt register; ++ * @mask: top aggregation interrupt common mask; ++ * @cell_low: location of low aggregation interrupt register; ++ * @mask_low: low aggregation interrupt common mask; ++ */ ++struct mlxreg_core_hotplug_platform_data { ++ struct mlxreg_core_item *items; ++ int irq; ++ void *regmap; ++ int counter; ++ u32 cell; ++ u32 mask; ++ u32 cell_low; ++ u32 mask_low; ++}; ++ ++#endif /* __LINUX_PLATFORM_DATA_MLXREG_H */ diff --git a/patch/0005-leds-add-driver-for-support-Mellanox-regmap-LEDs-for.patch b/patch/0005-leds-add-driver-for-support-Mellanox-regmap-LEDs-for.patch new file mode 100644 index 000000000..558cbbc4e --- /dev/null +++ b/patch/0005-leds-add-driver-for-support-Mellanox-regmap-LEDs-for.patch @@ -0,0 +1,398 @@ +Linux backport patch. Includes following commits: +7dc37aeb560416771cbdc286357157c7565dc1fe +9244ef4cb79a8411656cb8fc2366f32f2294a0c9 +daf155fe70c9d69c28bba632b6a758ac8feab6e7 + + +diff -Nur a/drivers/leds/Kconfig b/drivers/leds/Kconfig +--- a/drivers/leds/Kconfig 2017-11-12 09:08:40.740087699 +0000 ++++ b/drivers/leds/Kconfig 2017-11-12 09:06:54.580086289 +0000 +@@ -659,6 +659,35 @@ + This option enabled support for the LEDs on the Mellanox + boards. Say Y to enabled these. + ++config LEDS_MLXREG ++ tristate "LED support for the Mellanox BMC cards" ++ depends on LEDS_CLASS ++ help ++ This option enabled support for the LEDs on the Mellanox BMC cards. ++ The driver can be activated from the device tree or by the direct ++ platform device add call. Say Y to enabled these. To compile this ++ driver as a module, choose 'M' here: the module will be called ++ leds-mlxreg. ++ ++config LEDS_USER ++ tristate "Userspace LED support" ++ depends on LEDS_CLASS ++ help ++ This option enables support for userspace LEDs. Say 'y' to enable this ++ support in kernel. To compile this driver as a module, choose 'm' here: ++ the module will be called uleds. ++ ++config LEDS_NIC78BX ++ tristate "LED support for NI PXI NIC78bx devices" ++ depends on LEDS_CLASS ++ depends on X86 && ACPI ++ help ++ This option enables support for the User1 and User2 LEDs on NI ++ PXI NIC78bx devices. ++ ++ To compile this driver as a module, choose M here: the module ++ will be called leds-nic78bx. ++ + comment "LED Triggers" + source "drivers/leds/trigger/Kconfig" + +diff -Nur a/drivers/leds/Makefile b/drivers/leds/Makefile +--- a/drivers/leds/Makefile 2017-11-12 09:08:40.740087699 +0000 ++++ b/drivers/leds/Makefile 2017-11-12 09:06:54.580086289 +0000 +@@ -71,6 +71,7 @@ + obj-$(CONFIG_LEDS_IS31FL32XX) += leds-is31fl32xx.o + obj-$(CONFIG_LEDS_PM8058) += leds-pm8058.o + obj-$(CONFIG_LEDS_MLXCPLD) += leds-mlxcpld.o ++obj-$(CONFIG_LEDS_MLXREG) += leds-mlxreg.o + + # LED SPI Drivers + obj-$(CONFIG_LEDS_DAC124S085) += leds-dac124s085.o +diff -Nur a/drivers/leds/leds-mlxcpld.c b/drivers/leds/leds-mlxcpld.c +--- a/drivers/leds/leds-mlxcpld.c 2017-11-12 09:08:40.740087699 +0000 ++++ b/drivers/leds/leds-mlxcpld.c 2017-11-12 09:08:05.620087233 +0000 +@@ -400,6 +400,9 @@ + struct platform_device *pdev; + int err; + ++ if (!dmi_match(DMI_CHASSIS_VENDOR, "Mellanox Technologies Ltd.")) ++ return -ENODEV; ++ + pdev = platform_device_register_simple(KBUILD_MODNAME, -1, NULL, 0); + if (IS_ERR(pdev)) { + pr_err("Device allocation failed\n"); +@@ -426,5 +429,5 @@ + + MODULE_AUTHOR("Vadim Pasternak "); + MODULE_DESCRIPTION("Mellanox board LED driver"); +-MODULE_LICENSE("GPL v2"); ++MODULE_LICENSE("Dual BSD/GPL"); + MODULE_ALIAS("platform:leds_mlxcpld"); +diff -Nur a/drivers/leds/leds-mlxreg.c b/drivers/leds/leds-mlxreg.c +--- a/drivers/leds/leds-mlxreg.c 1970-01-01 00:00:00.000000000 +0000 ++++ b/drivers/leds/leds-mlxreg.c 2017-11-12 09:06:54.580086289 +0000 +@@ -0,0 +1,318 @@ ++/* ++ * Copyright (c) 2017 Mellanox Technologies. All rights reserved. ++ * Copyright (c) 2017 Vadim Pasternak ++ * ++ * Redistribution and use in source and binary forms, with or without ++ * modification, are permitted provided that the following conditions are met: ++ * ++ * 1. Redistributions of source code must retain the above copyright ++ * notice, this list of conditions and the following disclaimer. ++ * 2. Redistributions in binary form must reproduce the above copyright ++ * notice, this list of conditions and the following disclaimer in the ++ * documentation and/or other materials provided with the distribution. ++ * 3. Neither the names of the copyright holders nor the names of its ++ * contributors may be used to endorse or promote products derived from ++ * this software without specific prior written permission. ++ * ++ * Alternatively, this software may be distributed under the terms of the ++ * GNU General Public License ("GPL") version 2 as published by the Free ++ * Software Foundation. ++ * ++ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" ++ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE ++ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ++ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE ++ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR ++ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF ++ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS ++ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN ++ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ++ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE ++ * POSSIBILITY OF SUCH DAMAGE. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++/* Codes for LEDs. */ ++#define MLXREG_LED_OFFSET_BLINK_3HZ 0x01 /* Offset from solid: 3Hz blink */ ++#define MLXREG_LED_OFFSET_BLINK_6HZ 0x02 /* Offset from solid: 6Hz blink */ ++#define MLXREG_LED_IS_OFF 0x00 /* Off */ ++#define MLXREG_LED_RED_SOLID 0x05 /* Solid red */ ++#define MLXREG_LED_GREEN_SOLID 0x0D /* Solid green */ ++#define MLXREG_LED_AMBER_SOLID 0x09 /* Solid amber */ ++#define MLXREG_LED_BLINK_3HZ 167 /* ~167 msec off/on - HW support */ ++#define MLXREG_LED_BLINK_6HZ 83 /* ~83 msec off/on - HW support */ ++ ++/** ++ * struct mlxreg_led_data - led control data: ++ * ++ * @data: led configuration data; ++ * @led_classdev: led class data; ++ * @base_color: base led color (other colors have constant offset from base); ++ * @led_data: led data; ++ * @data_parent: pointer to private device control data of parent; ++ */ ++struct mlxreg_led_data { ++ struct mlxreg_core_data *data; ++ struct led_classdev led_cdev; ++ u8 base_color; ++ void *data_parent; ++ char led_cdev_name[MLXREG_CORE_LABEL_MAX_SIZE]; ++}; ++ ++#define cdev_to_priv(c) container_of(c, struct mlxreg_led_data, led_cdev) ++ ++/** ++ * struct mlxreg_led_priv_data - platform private data: ++ * ++ * @pdev: platform device; ++ * @pdata: platform data; ++ * @access_lock: mutex for attribute IO access; ++ */ ++struct mlxreg_led_priv_data { ++ struct platform_device *pdev; ++ struct mlxreg_core_led_platform_data *pdata; ++ struct mutex access_lock; /* protect IO operations */ ++}; ++ ++static int ++mlxreg_led_store_hw(struct mlxreg_led_data *led_data, u8 vset) ++{ ++ struct mlxreg_led_priv_data *priv = led_data->data_parent; ++ struct mlxreg_core_led_platform_data *led_pdata = priv->pdata; ++ struct mlxreg_core_data *data = led_data->data; ++ u32 regval; ++ u32 nib; ++ int ret; ++ ++ /* ++ * Each LED is controlled through low or high nibble of the relevant ++ * register byte. Register offset is specified by off parameter. ++ * Parameter vset provides color code: 0x0 for off, 0x5 for solid red, ++ * 0x6 for 3Hz blink red, 0xd for solid green, 0xe for 3Hz blink ++ * green. ++ * Parameter mask specifies which nibble is used for specific LED: mask ++ * 0xf0 - lower nibble is to be used (bits from 0 to 3), mask 0x0f - ++ * higher nibble (bits from 4 to 7). ++ */ ++ mutex_lock(&priv->access_lock); ++ ++ ret = regmap_read(led_pdata->regmap, data->reg, ®val); ++ if (ret) ++ goto access_error; ++ ++ nib = (ror32(data->mask, data->bit) == 0xf0) ? rol32(vset, data->bit) : ++ rol32(vset, data->bit + 4); ++ regval = (regval & data->mask) | nib; ++ ++ ret = regmap_write(led_pdata->regmap, data->reg, regval); ++ ++access_error: ++ mutex_unlock(&priv->access_lock); ++ ++ return ret; ++} ++ ++static enum led_brightness ++mlxreg_led_get_hw(struct mlxreg_led_data *led_data) ++{ ++ struct mlxreg_led_priv_data *priv = led_data->data_parent; ++ struct mlxreg_core_led_platform_data *led_pdata = priv->pdata; ++ struct mlxreg_core_data *data = led_data->data; ++ u32 regval; ++ int ret; ++ ++ /* ++ * Each LED is controlled through low or high nibble of the relevant ++ * register byte. Register offset is specified by off parameter. ++ * Parameter vset provides color code: 0x0 for off, 0x5 for solid red, ++ * 0x6 for 3Hz blink red, 0xd for solid green, 0xe for 3Hz blink ++ * green. ++ * Parameter mask specifies which nibble is used for specific LED: mask ++ * 0xf0 - lower nibble is to be used (bits from 0 to 3), mask 0x0f - ++ * higher nibble (bits from 4 to 7). ++ */ ++ ret = regmap_read(led_pdata->regmap, data->reg, ®val); ++ if (ret < 0) { ++ dev_warn(led_data->led_cdev.dev, "Failed to get current brightness, error: %d\n", ++ ret); ++ /* Assume the LED is OFF */ ++ return LED_OFF; ++ } ++ ++ regval = regval & ~data->mask; ++ regval = (ror32(data->mask, data->bit) == 0xf0) ? ror32(regval, ++ data->bit) : ror32(regval, data->bit + 4); ++ if (regval >= led_data->base_color && ++ regval <= (led_data->base_color + MLXREG_LED_OFFSET_BLINK_6HZ)) ++ ret = LED_FULL; ++ else ++ ret = LED_OFF; ++ ++ return ret; ++} ++ ++static int ++mlxreg_led_brightness_set(struct led_classdev *cled, enum led_brightness value) ++{ ++ struct mlxreg_led_data *led_data = cdev_to_priv(cled); ++ ++ if (value) ++ return mlxreg_led_store_hw(led_data, led_data->base_color); ++ else ++ return mlxreg_led_store_hw(led_data, MLXREG_LED_IS_OFF); ++} ++ ++static enum led_brightness ++mlxreg_led_brightness_get(struct led_classdev *cled) ++{ ++ struct mlxreg_led_data *led_data = cdev_to_priv(cled); ++ ++ return mlxreg_led_get_hw(led_data); ++} ++ ++static int ++mlxreg_led_blink_set(struct led_classdev *cled, unsigned long *delay_on, ++ unsigned long *delay_off) ++{ ++ struct mlxreg_led_data *led_data = cdev_to_priv(cled); ++ int err; ++ ++ /* ++ * HW supports two types of blinking: full (6Hz) and half (3Hz). ++ * For delay on/off zero LED is setting to solid color. For others ++ * combination blinking is to be controlled by the software timer. ++ */ ++ if (!(*delay_on == 0 && *delay_off == 0) && ++ !(*delay_on == MLXREG_LED_BLINK_3HZ && ++ *delay_off == MLXREG_LED_BLINK_3HZ) && ++ !(*delay_on == MLXREG_LED_BLINK_6HZ && ++ *delay_off == MLXREG_LED_BLINK_6HZ)) ++ return -EINVAL; ++ ++ if (*delay_on == MLXREG_LED_BLINK_6HZ) ++ err = mlxreg_led_store_hw(led_data, led_data->base_color + ++ MLXREG_LED_OFFSET_BLINK_6HZ); ++ else if (*delay_on == MLXREG_LED_BLINK_3HZ) ++ err = mlxreg_led_store_hw(led_data, led_data->base_color + ++ MLXREG_LED_OFFSET_BLINK_3HZ); ++ else ++ err = mlxreg_led_store_hw(led_data, led_data->base_color); ++ ++ return err; ++} ++ ++static int mlxreg_led_config(struct mlxreg_led_priv_data *priv) ++{ ++ struct mlxreg_core_led_platform_data *led_pdata = priv->pdata; ++ struct mlxreg_core_data *data = led_pdata->data; ++ struct mlxreg_led_data *led_data; ++ struct led_classdev *led_cdev; ++ int brightness; ++ int i; ++ int err; ++ ++ for (i = 0; i < led_pdata->counter; i++, data++) { ++ led_data = devm_kzalloc(&priv->pdev->dev, sizeof(*led_data), ++ GFP_KERNEL); ++ if (!led_data) ++ return -ENOMEM; ++ ++ led_cdev = &led_data->led_cdev; ++ led_data->data_parent = priv; ++ if (strstr(data->label, "red") || ++ strstr(data->label, "orange")) { ++ brightness = LED_OFF; ++ led_data->base_color = MLXREG_LED_RED_SOLID; ++ } else if (strstr(data->label, "amber")) { ++ brightness = LED_OFF; ++ led_data->base_color = MLXREG_LED_AMBER_SOLID; ++ } else { ++ brightness = LED_OFF; ++ led_data->base_color = MLXREG_LED_GREEN_SOLID; ++ } ++ sprintf(led_data->led_cdev_name, "%s:%s", "mlxreg", ++ data->label); ++ led_cdev->name = led_data->led_cdev_name; ++ led_cdev->brightness = brightness; ++ led_cdev->max_brightness = 1; ++ led_cdev->brightness_set_blocking = ++ mlxreg_led_brightness_set; ++ led_cdev->brightness_get = mlxreg_led_brightness_get; ++ led_cdev->blink_set = mlxreg_led_blink_set; ++ led_cdev->flags = LED_CORE_SUSPENDRESUME; ++ led_data->data = data; ++ err = devm_led_classdev_register(&priv->pdev->dev, led_cdev); ++ if (err) ++ return err; ++ ++ if (led_cdev->brightness) ++ mlxreg_led_brightness_set(led_cdev, ++ led_cdev->brightness); ++ dev_info(led_cdev->dev, "label: %s, mask: 0x%02x, offset:0x%02x\n", ++ data->label, data->mask, data->reg); ++ } ++ ++ return 0; ++} ++ ++static int mlxreg_led_probe(struct platform_device *pdev) ++{ ++ struct mlxreg_core_led_platform_data *led_pdata; ++ struct mlxreg_led_priv_data *priv; ++ ++ led_pdata = dev_get_platdata(&pdev->dev); ++ if (!led_pdata) { ++ dev_err(&pdev->dev, "Failed to get platform data.\n"); ++ return -EINVAL; ++ } ++ ++ priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); ++ if (!priv) ++ return -ENOMEM; ++ ++ mutex_init(&priv->access_lock); ++ priv->pdev = pdev; ++ priv->pdata = led_pdata; ++ ++ return mlxreg_led_config(priv); ++} ++ ++static int mlxreg_led_remove(struct platform_device *pdev) ++{ ++ struct mlxreg_led_priv_data *priv = dev_get_drvdata(&pdev->dev); ++ ++ mutex_destroy(&priv->access_lock); ++ ++ return 0; ++} ++ ++static const struct of_device_id mlxreg_led_dt_match[] = { ++ { .compatible = "mellanox,leds-mlxreg" }, ++ { }, ++}; ++MODULE_DEVICE_TABLE(of, mlxreg_led_dt_match); ++ ++static struct platform_driver mlxreg_led_driver = { ++ .driver = { ++ .name = "leds-mlxreg", ++ .of_match_table = of_match_ptr(mlxreg_led_dt_match), ++ }, ++ .probe = mlxreg_led_probe, ++ .remove = mlxreg_led_remove, ++}; ++ ++module_platform_driver(mlxreg_led_driver); ++ ++MODULE_AUTHOR("Vadim Pasternak "); ++MODULE_DESCRIPTION("Mellanox LED regmap driver"); ++MODULE_LICENSE("Dual BSD/GPL"); ++MODULE_ALIAS("platform:leds-mlxreg"); diff --git a/patch/0006-Mellanox-switch-drivers-changes.patch b/patch/0006-Mellanox-switch-drivers-changes.patch new file mode 100644 index 000000000..c820bbdc0 --- /dev/null +++ b/patch/0006-Mellanox-switch-drivers-changes.patch @@ -0,0 +1,2118 @@ +Linux backport patch. Includes following commits: +02f1d19ecd08f7da83bf17d556ba147b16ed9dab +50deb9064015956274a989d035c0a101188c5bf2 +515dc42e5f57aa4b2dbb18fbe6b3200224d051a7 +63b01357d3f002ebed8e532b503a84dde6f45060 +7c2ed7426f0835d5408a2c97fc6ebcc67c5feea0 +65f178307a4274f1ab52f4e729e1eabe00ee2d96 +81ce6e3fefba4ed1578db80609b54ccc3ae624cb +3f65860a8b01652fbda978b991d13c02848c8ee2 +05cdb2439ba8bb00a1746ec68e27cec62ea1e142 +021697a48b00b51636d88e5056015ad65b6da821 +f6410966453b7671a0c4032652db36b2e67ba43c +acf30a9f0714a734531078b7a6d85ab7762c3589 +f334341a185bad33bbc3cf0a3b7d7189d8803bc0 +aec592f5c0d44b3ac4038dc539859fa247738f6e +589428b6233c6a9bffbf8c8bca86f62838f35021 + + +diff -Nur a/drivers/net/ethernet/mellanox/mlxsw/Kconfig b/drivers/net/ethernet/mellanox/mlxsw/Kconfig +--- a/drivers/net/ethernet/mellanox/mlxsw/Kconfig 2017-05-25 13:45:05.000000000 +0000 ++++ b/drivers/net/ethernet/mellanox/mlxsw/Kconfig 2017-11-09 12:40:31.940814834 +0000 +@@ -19,6 +19,24 @@ + ---help--- + Say Y here if you want to expose HWMON interface on mlxsw devices. + ++config MLXSW_CORE_THERMAL ++ bool "Thermal zone support for Mellanox Technologies Switch ASICs" ++ depends on MLXSW_CORE && THERMAL ++ depends on !(MLXSW_CORE=y && THERMAL=m) ++ default y ++ ---help--- ++ Say Y here if you want to automatically control fans speed according ++ ambient temperature reported by ASIC. ++ ++config MLXSW_CORE_QSFP ++ bool "QSFP support for Mellanox Technologies Switch ASICs" ++ depends on MLXSW_CORE && HWMON ++ depends on !(MLXSW_CORE=y && HWMON=m) ++ default y ++ ---help--- ++ Say Y here if you want to expose sysfs QSFP interface on mlxsw ++ devices. ++ + config MLXSW_PCI + tristate "PCI bus implementation for Mellanox Technologies Switch ASICs" + depends on PCI && HAS_DMA && HAS_IOMEM && MLXSW_CORE +@@ -29,6 +47,27 @@ + To compile this driver as a module, choose M here: the + module will be called mlxsw_pci. + ++config MLXSW_I2C ++ tristate "I2C bus implementation for Mellanox Technologies Switch ASICs" ++ depends on I2C && MLXSW_CORE ++ default m ++ ---help--- ++ This is I2C bus implementation for Mellanox Technologies Switch ASICs. ++ ++ To compile this driver as a module, choose M here: the ++ module will be called mlxsw_i2c. ++ ++config MLXSW_SWITCHIB ++ tristate "Mellanox Technologies SwitchIB and SwitchIB-2 support" ++ depends on MLXSW_CORE && NET_SWITCHDEV ++ default m ++ ---help--- ++ This driver supports Mellanox Technologies SwitchIB and SwitchIB-2 ++ Infiniband Switch ASICs. ++ ++ To compile this driver as a module, choose M here: the ++ module will be called mlxsw_switchib. ++ + config MLXSW_SWITCHX2 + tristate "Mellanox Technologies SwitchX-2 support" + depends on MLXSW_CORE && NET_SWITCHDEV +@@ -58,3 +97,14 @@ + ---help--- + Say Y here if you want to use Data Center Bridging (DCB) in the + driver. ++ ++config MLXSW_MINIMAL ++ tristate "Mellanox Technologies minimal I2C support" ++ depends on MLXSW_CORE && MLXSW_I2C ++ default m ++ ---help--- ++ This driver supports I2C access for Mellanox Technologies Switch ++ ASICs. ++ ++ To compile this driver as a module, choose M here: the ++ module will be called mlxsw_minimal. +diff -Nur a/drivers/net/ethernet/mellanox/mlxsw/Makefile b/drivers/net/ethernet/mellanox/mlxsw/Makefile +--- a/drivers/net/ethernet/mellanox/mlxsw/Makefile 2017-05-25 13:45:05.000000000 +0000 ++++ b/drivers/net/ethernet/mellanox/mlxsw/Makefile 2017-11-09 12:40:31.940814834 +0000 +@@ -1,8 +1,14 @@ + obj-$(CONFIG_MLXSW_CORE) += mlxsw_core.o + mlxsw_core-objs := core.o + mlxsw_core-$(CONFIG_MLXSW_CORE_HWMON) += core_hwmon.o ++mlxsw_core-$(CONFIG_MLXSW_CORE_THERMAL) += core_thermal.o ++mlxsw_core-$(CONFIG_MLXSW_CORE_QSFP) += qsfp_sysfs.o + obj-$(CONFIG_MLXSW_PCI) += mlxsw_pci.o + mlxsw_pci-objs := pci.o ++obj-$(CONFIG_MLXSW_I2C) += mlxsw_i2c.o ++mlxsw_i2c-objs := i2c.o ++obj-$(CONFIG_MLXSW_SWITCHIB) += mlxsw_switchib.o ++mlxsw_switchib-objs := switchib.o + obj-$(CONFIG_MLXSW_SWITCHX2) += mlxsw_switchx2.o + mlxsw_switchx2-objs := switchx2.o + obj-$(CONFIG_MLXSW_SPECTRUM) += mlxsw_spectrum.o +@@ -10,3 +16,5 @@ + spectrum_switchdev.o spectrum_router.o \ + spectrum_kvdl.o + mlxsw_spectrum-$(CONFIG_MLXSW_SPECTRUM_DCB) += spectrum_dcb.o ++obj-$(CONFIG_MLXSW_MINIMAL) += mlxsw_minimal.o ++mlxsw_minimal-objs := minimal.o +diff -Nur a/drivers/net/ethernet/mellanox/mlxsw/core.c b/drivers/net/ethernet/mellanox/mlxsw/core.c +--- a/drivers/net/ethernet/mellanox/mlxsw/core.c 2017-05-25 13:45:05.000000000 +0000 ++++ b/drivers/net/ethernet/mellanox/mlxsw/core.c 2017-11-09 13:03:45.824833341 +0000 +@@ -113,6 +113,9 @@ + } lag; + struct mlxsw_resources resources; + struct mlxsw_hwmon *hwmon; ++ struct mlxsw_thermal *thermal; ++struct mlxsw_qsfp *qsfp; ++ struct mlxsw_core_port ports[MLXSW_PORT_MAX_PORTS]; + unsigned long driver_priv[0]; + /* driver_priv has to be always the last item */ + }; +@@ -579,6 +582,9 @@ + u64 tid; + int err; + ++ if (!(mlxsw_core->bus->features & MLXSW_BUS_F_TXRX)) ++ return 0; ++ + /* Set the upper 32 bits of the transaction ID field to a random + * number. This allows us to discard EMADs addressed to other + * devices. +@@ -615,6 +621,9 @@ + { + char hpkt_pl[MLXSW_REG_HPKT_LEN]; + ++ if (!(mlxsw_core->bus->features & MLXSW_BUS_F_TXRX)) ++ return; ++ + mlxsw_core->emad.use_emad = false; + mlxsw_reg_hpkt_pack(hpkt_pl, MLXSW_REG_HPKT_ACTION_DISCARD, + MLXSW_TRAP_ID_ETHEMAD); +@@ -1128,9 +1137,21 @@ + if (err) + goto err_hwmon_init; + +- err = mlxsw_driver->init(mlxsw_core, mlxsw_bus_info); ++ err = mlxsw_thermal_init(mlxsw_core, mlxsw_bus_info, ++ &mlxsw_core->thermal); + if (err) +- goto err_driver_init; ++ goto err_thermal_init; ++ ++ err = mlxsw_qsfp_init(mlxsw_core, mlxsw_bus_info, ++ &mlxsw_core->qsfp); ++ if (err) ++ goto err_qsfp_init; ++ ++ if (mlxsw_driver->init) { ++ err = mlxsw_driver->init(mlxsw_core, mlxsw_bus_info); ++ if (err) ++ goto err_driver_init; ++ } + + err = mlxsw_core_debugfs_init(mlxsw_core); + if (err) +@@ -1141,6 +1162,10 @@ + err_debugfs_init: + mlxsw_core->driver->fini(mlxsw_core); + err_driver_init: ++ mlxsw_qsfp_fini(mlxsw_core->qsfp); ++err_qsfp_init: ++ mlxsw_thermal_fini(mlxsw_core->thermal); ++err_thermal_init: + err_hwmon_init: + devlink_unregister(devlink); + err_devlink_register: +@@ -1165,7 +1190,10 @@ + struct devlink *devlink = priv_to_devlink(mlxsw_core); + + mlxsw_core_debugfs_fini(mlxsw_core); +- mlxsw_core->driver->fini(mlxsw_core); ++ if (mlxsw_core->driver->fini) ++ mlxsw_core->driver->fini(mlxsw_core); ++ mlxsw_qsfp_fini(mlxsw_core->qsfp); ++ mlxsw_thermal_fini(mlxsw_core->thermal); + devlink_unregister(devlink); + mlxsw_emad_fini(mlxsw_core); + mlxsw_core->bus->fini(mlxsw_core->bus_priv); +diff -Nur a/drivers/net/ethernet/mellanox/mlxsw/core.h b/drivers/net/ethernet/mellanox/mlxsw/core.h +--- a/drivers/net/ethernet/mellanox/mlxsw/core.h 2017-05-25 13:45:05.000000000 +0000 ++++ b/drivers/net/ethernet/mellanox/mlxsw/core.h 2017-11-09 13:03:45.824833341 +0000 +@@ -300,6 +300,8 @@ + + struct mlxsw_resources *mlxsw_core_resources_get(struct mlxsw_core *mlxsw_core); + ++#define MLXSW_BUS_F_TXRX BIT(0) ++ + struct mlxsw_bus { + const char *kind; + int (*init)(void *bus_priv, struct mlxsw_core *mlxsw_core, +@@ -315,6 +317,7 @@ + char *in_mbox, size_t in_mbox_size, + char *out_mbox, size_t out_mbox_size, + u8 *p_status); ++ u8 features; + }; + + struct mlxsw_bus_info { +@@ -349,5 +352,53 @@ + } + + #endif ++ ++struct mlxsw_thermal; ++ ++#ifdef CONFIG_MLXSW_CORE_THERMAL ++ ++int mlxsw_thermal_init(struct mlxsw_core *mlxsw_core, ++ const struct mlxsw_bus_info *mlxsw_bus_info, ++ struct mlxsw_thermal **p_thermal); ++void mlxsw_thermal_fini(struct mlxsw_thermal *thermal); ++ ++#else ++ ++static inline int mlxsw_thermal_init(struct mlxsw_core *mlxsw_core, ++ const struct mlxsw_bus_info *mlxsw_bus_info, ++ struct mlxsw_thermal **p_thermal) ++{ ++ return 0; ++} ++ ++static inline void mlxsw_thermal_fini(struct mlxsw_thermal *thermal) ++{ ++} ++ ++#endif ++ ++struct mlxsw_qsfp; ++ ++#ifdef CONFIG_MLXSW_CORE_QSFP ++ ++int mlxsw_qsfp_init(struct mlxsw_core *mlxsw_core, ++ const struct mlxsw_bus_info *mlxsw_bus_info, ++ struct mlxsw_qsfp **p_qsfp); ++void mlxsw_qsfp_fini(struct mlxsw_qsfp *qsfp); ++ ++#else ++ ++static inline int mlxsw_qsfp_init(struct mlxsw_core *mlxsw_core, ++ const struct mlxsw_bus_info *mlxsw_bus_info, ++ struct mlxsw_qsfp **p_qsfp) ++{ ++ return 0; ++} ++ ++static inline void mlxsw_qsfp_fini(struct mlxsw_qsfp *qsfp) ++{ ++} ++ ++#endif + + #endif +diff -Nur a/drivers/net/ethernet/mellanox/mlxsw/core_hwmon.c b/drivers/net/ethernet/mellanox/mlxsw/core_hwmon.c +--- a/drivers/net/ethernet/mellanox/mlxsw/core_hwmon.c 2017-05-25 13:45:05.000000000 +0000 ++++ b/drivers/net/ethernet/mellanox/mlxsw/core_hwmon.c 2017-11-09 13:04:29.120833916 +0000 +@@ -262,7 +262,7 @@ + + static int mlxsw_hwmon_temp_init(struct mlxsw_hwmon *mlxsw_hwmon) + { +- char mtcap_pl[MLXSW_REG_MTCAP_LEN]; ++ char mtcap_pl[MLXSW_REG_MTCAP_LEN] = {0}; + char mtmp_pl[MLXSW_REG_MTMP_LEN]; + u8 sensor_count; + int i; +@@ -295,7 +295,7 @@ + + static int mlxsw_hwmon_fans_init(struct mlxsw_hwmon *mlxsw_hwmon) + { +- char mfcr_pl[MLXSW_REG_MFCR_LEN]; ++ char mfcr_pl[MLXSW_REG_MFCR_LEN] = {0}; + enum mlxsw_reg_mfcr_pwm_frequency freq; + unsigned int type_index; + unsigned int num; +diff -Nur a/drivers/net/ethernet/mellanox/mlxsw/core_thermal.c b/drivers/net/ethernet/mellanox/mlxsw/core_thermal.c +--- a/drivers/net/ethernet/mellanox/mlxsw/core_thermal.c 1970-01-01 00:00:00.000000000 +0000 ++++ b/drivers/net/ethernet/mellanox/mlxsw/core_thermal.c 2017-11-09 13:04:52.192834223 +0000 +@@ -0,0 +1,436 @@ ++/* ++ * drivers/net/ethernet/mellanox/mlxsw/core_thermal.c ++ * Copyright (c) 2016 Ivan Vecera ++ * ++ * Redistribution and use in source and binary forms, with or without ++ * modification, are permitted provided that the following conditions are met: ++ * ++ * 1. Redistributions of source code must retain the above copyright ++ * notice, this list of conditions and the following disclaimer. ++ * 2. Redistributions in binary form must reproduce the above copyright ++ * notice, this list of conditions and the following disclaimer in the ++ * documentation and/or other materials provided with the distribution. ++ * 3. Neither the names of the copyright holders nor the names of its ++ * contributors may be used to endorse or promote products derived from ++ * this software without specific prior written permission. ++ * ++ * Alternatively, this software may be distributed under the terms of the ++ * GNU General Public License ("GPL") version 2 as published by the Free ++ * Software Foundation. ++ * ++ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" ++ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE ++ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ++ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE ++ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR ++ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF ++ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS ++ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN ++ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ++ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE ++ * POSSIBILITY OF SUCH DAMAGE. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "core.h" ++ ++#define MLXSW_THERMAL_POLL_INT 1000 /* ms */ ++#define MLXSW_THERMAL_MAX_TEMP 110000 /* 110C */ ++#define MLXSW_THERMAL_MAX_STATE 10 ++#define MLXSW_THERMAL_MAX_DUTY 255 ++ ++struct mlxsw_thermal_trip { ++ int type; ++ int temp; ++ int min_state; ++ int max_state; ++}; ++ ++static const struct mlxsw_thermal_trip default_thermal_trips[] = { ++ { /* Above normal - 60%-100% PWM */ ++ .type = THERMAL_TRIP_ACTIVE, ++ .temp = 75000, ++ .min_state = (6 * MLXSW_THERMAL_MAX_STATE) / 10, ++ .max_state = MLXSW_THERMAL_MAX_STATE, ++ }, ++ { ++ /* Very high - 100% PWM */ ++ .type = THERMAL_TRIP_ACTIVE, ++ .temp = 85000, ++ .min_state = MLXSW_THERMAL_MAX_STATE, ++ .max_state = MLXSW_THERMAL_MAX_STATE, ++ }, ++ { /* Warning */ ++ .type = THERMAL_TRIP_HOT, ++ .temp = 105000, ++ .min_state = MLXSW_THERMAL_MAX_STATE, ++ .max_state = MLXSW_THERMAL_MAX_STATE, ++ }, ++ { /* Critical - soft poweroff */ ++ .type = THERMAL_TRIP_CRITICAL, ++ .temp = MLXSW_THERMAL_MAX_TEMP, ++ .min_state = MLXSW_THERMAL_MAX_STATE, ++ .max_state = MLXSW_THERMAL_MAX_STATE, ++ } ++}; ++ ++#define MLXSW_THERMAL_NUM_TRIPS ARRAY_SIZE(default_thermal_trips) ++ ++/* Make sure all trips are writable */ ++#define MLXSW_THERMAL_TRIP_MASK (BIT(MLXSW_THERMAL_NUM_TRIPS) - 1) ++ ++struct mlxsw_thermal { ++ struct mlxsw_core *core; ++ const struct mlxsw_bus_info *bus_info; ++ struct thermal_zone_device *tzdev; ++ struct thermal_cooling_device *cdevs[MLXSW_MFCR_PWMS_MAX]; ++ struct mlxsw_thermal_trip trips[MLXSW_THERMAL_NUM_TRIPS]; ++ enum thermal_device_mode mode; ++}; ++ ++static inline u8 mlxsw_state_to_duty(int state) ++{ ++ return DIV_ROUND_CLOSEST(state * MLXSW_THERMAL_MAX_DUTY, ++ MLXSW_THERMAL_MAX_STATE); ++} ++ ++static inline int mlxsw_duty_to_state(u8 duty) ++{ ++ return DIV_ROUND_CLOSEST(duty * MLXSW_THERMAL_MAX_STATE, ++ MLXSW_THERMAL_MAX_DUTY); ++} ++ ++static int mlxsw_get_cooling_device_idx(struct mlxsw_thermal *thermal, ++ struct thermal_cooling_device *cdev) ++{ ++ int i; ++ ++ for (i = 0; i < MLXSW_MFCR_PWMS_MAX; i++) ++ if (thermal->cdevs[i] == cdev) ++ return i; ++ ++ return -ENODEV; ++} ++ ++static int mlxsw_thermal_bind(struct thermal_zone_device *tzdev, ++ struct thermal_cooling_device *cdev) ++{ ++ struct mlxsw_thermal *thermal = tzdev->devdata; ++ struct device *dev = thermal->bus_info->dev; ++ int i, err; ++ ++ /* If the cooling device is one of ours bind it */ ++ if (mlxsw_get_cooling_device_idx(thermal, cdev) < 0) ++ return 0; ++ ++ for (i = 0; i < MLXSW_THERMAL_NUM_TRIPS; i++) { ++ const struct mlxsw_thermal_trip *trip = &thermal->trips[i]; ++ ++ err = thermal_zone_bind_cooling_device(tzdev, i, cdev, ++ trip->max_state, ++ trip->min_state, ++ THERMAL_WEIGHT_DEFAULT); ++ if (err < 0) { ++ dev_err(dev, "Failed to bind cooling device to trip %d\n", i); ++ return err; ++ } ++ } ++ return 0; ++} ++ ++static int mlxsw_thermal_unbind(struct thermal_zone_device *tzdev, ++ struct thermal_cooling_device *cdev) ++{ ++ struct mlxsw_thermal *thermal = tzdev->devdata; ++ struct device *dev = thermal->bus_info->dev; ++ int i; ++ int err; ++ ++ /* If the cooling device is our one unbind it */ ++ if (mlxsw_get_cooling_device_idx(thermal, cdev) < 0) ++ return 0; ++ ++ for (i = 0; i < MLXSW_THERMAL_NUM_TRIPS; i++) { ++ err = thermal_zone_unbind_cooling_device(tzdev, i, cdev); ++ if (err < 0) { ++ dev_err(dev, "Failed to unbind cooling device\n"); ++ return err; ++ } ++ } ++ return 0; ++} ++ ++static int mlxsw_thermal_get_mode(struct thermal_zone_device *tzdev, ++ enum thermal_device_mode *mode) ++{ ++ struct mlxsw_thermal *thermal = tzdev->devdata; ++ ++ *mode = thermal->mode; ++ ++ return 0; ++} ++ ++static int mlxsw_thermal_set_mode(struct thermal_zone_device *tzdev, ++ enum thermal_device_mode mode) ++{ ++ struct mlxsw_thermal *thermal = tzdev->devdata; ++ ++ mutex_lock(&tzdev->lock); ++ ++ if (mode == THERMAL_DEVICE_ENABLED) ++ tzdev->polling_delay = MLXSW_THERMAL_POLL_INT; ++ else ++ tzdev->polling_delay = 0; ++ ++ mutex_unlock(&tzdev->lock); ++ ++ thermal->mode = mode; ++ thermal_zone_device_update(tzdev, THERMAL_EVENT_UNSPECIFIED); ++ ++ return 0; ++} ++ ++static int mlxsw_thermal_get_temp(struct thermal_zone_device *tzdev, ++ int *p_temp) ++{ ++ struct mlxsw_thermal *thermal = tzdev->devdata; ++ struct device *dev = thermal->bus_info->dev; ++ char mtmp_pl[MLXSW_REG_MTMP_LEN]; ++ unsigned int temp; ++ int err; ++ ++ mlxsw_reg_mtmp_pack(mtmp_pl, 0, false, false); ++ ++ err = mlxsw_reg_query(thermal->core, MLXSW_REG(mtmp), mtmp_pl); ++ if (err) { ++ dev_err(dev, "Failed to query temp sensor\n"); ++ return err; ++ } ++ mlxsw_reg_mtmp_unpack(mtmp_pl, &temp, NULL, NULL); ++ ++ *p_temp = (int) temp; ++ return 0; ++} ++ ++static int mlxsw_thermal_get_trip_type(struct thermal_zone_device *tzdev, ++ int trip, ++ enum thermal_trip_type *p_type) ++{ ++ struct mlxsw_thermal *thermal = tzdev->devdata; ++ ++ if (trip < 0 || trip >= MLXSW_THERMAL_NUM_TRIPS) ++ return -EINVAL; ++ ++ *p_type = thermal->trips[trip].type; ++ return 0; ++} ++ ++static int mlxsw_thermal_get_trip_temp(struct thermal_zone_device *tzdev, ++ int trip, int *p_temp) ++{ ++ struct mlxsw_thermal *thermal = tzdev->devdata; ++ ++ if (trip < 0 || trip >= MLXSW_THERMAL_NUM_TRIPS) ++ return -EINVAL; ++ ++ *p_temp = thermal->trips[trip].temp; ++ return 0; ++} ++ ++static int mlxsw_thermal_set_trip_temp(struct thermal_zone_device *tzdev, ++ int trip, int temp) ++{ ++ struct mlxsw_thermal *thermal = tzdev->devdata; ++ ++ if (trip < 0 || trip >= MLXSW_THERMAL_NUM_TRIPS || ++ temp > MLXSW_THERMAL_MAX_TEMP) ++ return -EINVAL; ++ ++ thermal->trips[trip].temp = temp; ++ return 0; ++} ++ ++static struct thermal_zone_device_ops mlxsw_thermal_ops = { ++ .bind = mlxsw_thermal_bind, ++ .unbind = mlxsw_thermal_unbind, ++ .get_mode = mlxsw_thermal_get_mode, ++ .set_mode = mlxsw_thermal_set_mode, ++ .get_temp = mlxsw_thermal_get_temp, ++ .get_trip_type = mlxsw_thermal_get_trip_type, ++ .get_trip_temp = mlxsw_thermal_get_trip_temp, ++ .set_trip_temp = mlxsw_thermal_set_trip_temp, ++}; ++ ++static int mlxsw_thermal_get_max_state(struct thermal_cooling_device *cdev, ++ unsigned long *p_state) ++{ ++ *p_state = MLXSW_THERMAL_MAX_STATE; ++ return 0; ++} ++ ++static int mlxsw_thermal_get_cur_state(struct thermal_cooling_device *cdev, ++ unsigned long *p_state) ++ ++{ ++ struct mlxsw_thermal *thermal = cdev->devdata; ++ struct device *dev = thermal->bus_info->dev; ++ char mfsc_pl[MLXSW_REG_MFSC_LEN]; ++ int err, idx; ++ u8 duty; ++ ++ idx = mlxsw_get_cooling_device_idx(thermal, cdev); ++ if (idx < 0) ++ return idx; ++ ++ mlxsw_reg_mfsc_pack(mfsc_pl, idx, 0); ++ err = mlxsw_reg_query(thermal->core, MLXSW_REG(mfsc), mfsc_pl); ++ if (err) { ++ dev_err(dev, "Failed to query PWM duty\n"); ++ return err; ++ } ++ ++ duty = mlxsw_reg_mfsc_pwm_duty_cycle_get(mfsc_pl); ++ *p_state = mlxsw_duty_to_state(duty); ++ return 0; ++} ++ ++static int mlxsw_thermal_set_cur_state(struct thermal_cooling_device *cdev, ++ unsigned long state) ++ ++{ ++ struct mlxsw_thermal *thermal = cdev->devdata; ++ struct device *dev = thermal->bus_info->dev; ++ char mfsc_pl[MLXSW_REG_MFSC_LEN]; ++ int err, idx; ++ ++ idx = mlxsw_get_cooling_device_idx(thermal, cdev); ++ if (idx < 0) ++ return idx; ++ ++ mlxsw_reg_mfsc_pack(mfsc_pl, idx, mlxsw_state_to_duty(state)); ++ err = mlxsw_reg_write(thermal->core, MLXSW_REG(mfsc), mfsc_pl); ++ if (err) { ++ dev_err(dev, "Failed to write PWM duty\n"); ++ return err; ++ } ++ return 0; ++} ++ ++static const struct thermal_cooling_device_ops mlxsw_cooling_ops = { ++ .get_max_state = mlxsw_thermal_get_max_state, ++ .get_cur_state = mlxsw_thermal_get_cur_state, ++ .set_cur_state = mlxsw_thermal_set_cur_state, ++}; ++ ++int mlxsw_thermal_init(struct mlxsw_core *core, ++ const struct mlxsw_bus_info *bus_info, ++ struct mlxsw_thermal **p_thermal) ++{ ++ char mfcr_pl[MLXSW_REG_MFCR_LEN] = { 0 }; ++ enum mlxsw_reg_mfcr_pwm_frequency freq; ++ struct device *dev = bus_info->dev; ++ struct mlxsw_thermal *thermal; ++ u16 tacho_active; ++ u8 pwm_active; ++ int err, i; ++ ++ thermal = devm_kzalloc(dev, sizeof(*thermal), ++ GFP_KERNEL); ++ if (!thermal) ++ return -ENOMEM; ++ ++ thermal->core = core; ++ thermal->bus_info = bus_info; ++ memcpy(thermal->trips, default_thermal_trips, sizeof(thermal->trips)); ++ ++ err = mlxsw_reg_query(thermal->core, MLXSW_REG(mfcr), mfcr_pl); ++ if (err) { ++ dev_err(dev, "Failed to probe PWMs\n"); ++ goto err_free_thermal; ++ } ++ mlxsw_reg_mfcr_unpack(mfcr_pl, &freq, &tacho_active, &pwm_active); ++ ++ for (i = 0; i < MLXSW_MFCR_TACHOS_MAX; i++) { ++ if (tacho_active & BIT(i)) { ++ char mfsl_pl[MLXSW_REG_MFSL_LEN]; ++ ++ mlxsw_reg_mfsl_pack(mfsl_pl, i, 0, 0); ++ ++ /* We need to query the register to preserve maximum */ ++ err = mlxsw_reg_query(thermal->core, MLXSW_REG(mfsl), ++ mfsl_pl); ++ if (err) ++ goto err_free_thermal; ++ ++ /* set the minimal RPMs to 0 */ ++ mlxsw_reg_mfsl_tach_min_set(mfsl_pl, 0); ++ err = mlxsw_reg_write(thermal->core, MLXSW_REG(mfsl), ++ mfsl_pl); ++ if (err) ++ goto err_free_thermal; ++ } ++ } ++ for (i = 0; i < MLXSW_MFCR_PWMS_MAX; i++) { ++ if (pwm_active & BIT(i)) { ++ struct thermal_cooling_device *cdev; ++ ++ cdev = thermal_cooling_device_register("Fan", thermal, ++ &mlxsw_cooling_ops); ++ if (IS_ERR(cdev)) { ++ err = PTR_ERR(cdev); ++ dev_err(dev, "Failed to register cooling device\n"); ++ goto err_unreg_cdevs; ++ } ++ thermal->cdevs[i] = cdev; ++ } ++ } ++ ++ thermal->tzdev = thermal_zone_device_register("mlxsw", ++ MLXSW_THERMAL_NUM_TRIPS, ++ MLXSW_THERMAL_TRIP_MASK, ++ thermal, ++ &mlxsw_thermal_ops, ++ NULL, 0, ++ MLXSW_THERMAL_POLL_INT); ++ if (IS_ERR(thermal->tzdev)) { ++ err = PTR_ERR(thermal->tzdev); ++ dev_err(dev, "Failed to register thermal zone\n"); ++ goto err_unreg_cdevs; ++ } ++ ++ thermal->mode = THERMAL_DEVICE_ENABLED; ++ *p_thermal = thermal; ++ return 0; ++err_unreg_cdevs: ++ for (i = 0; i < MLXSW_MFCR_PWMS_MAX; i++) ++ if (thermal->cdevs[i]) ++ thermal_cooling_device_unregister(thermal->cdevs[i]); ++err_free_thermal: ++ devm_kfree(dev, thermal); ++ return err; ++} ++ ++void mlxsw_thermal_fini(struct mlxsw_thermal *thermal) ++{ ++ int i; ++ ++ if (thermal->tzdev) { ++ thermal_zone_device_unregister(thermal->tzdev); ++ thermal->tzdev = NULL; ++ } ++ ++ for (i = 0; i < MLXSW_MFCR_PWMS_MAX; i++) { ++ if (thermal->cdevs[i]) { ++ thermal_cooling_device_unregister(thermal->cdevs[i]); ++ thermal->cdevs[i] = NULL; ++ } ++ } ++ ++ devm_kfree(thermal->bus_info->dev, thermal); ++} +diff -Nur a/drivers/net/ethernet/mellanox/mlxsw/i2c.c b/drivers/net/ethernet/mellanox/mlxsw/i2c.c +--- a/drivers/net/ethernet/mellanox/mlxsw/i2c.c 1970-01-01 00:00:00.000000000 +0000 ++++ b/drivers/net/ethernet/mellanox/mlxsw/i2c.c 2017-11-09 13:04:29.120833916 +0000 +@@ -0,0 +1,582 @@ ++/* ++ * drivers/net/ethernet/mellanox/mlxsw/i2c.c ++ * Copyright (c) 2016 Mellanox Technologies. All rights reserved. ++ * Copyright (c) 2016 Vadim Pasternak ++ * ++ * Redistribution and use in source and binary forms, with or without ++ * modification, are permitted provided that the following conditions are met: ++ * ++ * 1. Redistributions of source code must retain the above copyright ++ * notice, this list of conditions and the following disclaimer. ++ * 2. Redistributions in binary form must reproduce the above copyright ++ * notice, this list of conditions and the following disclaimer in the ++ * documentation and/or other materials provided with the distribution. ++ * 3. Neither the names of the copyright holders nor the names of its ++ * contributors may be used to endorse or promote products derived from ++ * this software without specific prior written permission. ++ * ++ * Alternatively, this software may be distributed under the terms of the ++ * GNU General Public License ("GPL") version 2 as published by the Free ++ * Software Foundation. ++ * ++ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" ++ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE ++ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ++ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE ++ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR ++ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF ++ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS ++ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN ++ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ++ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE ++ * POSSIBILITY OF SUCH DAMAGE. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "cmd.h" ++#include "core.h" ++#include "i2c.h" ++ ++static const char mlxsw_i2c_driver_name[] = "mlxsw_i2c"; ++ ++#define MLXSW_I2C_CIR2_BASE 0x72000 ++#define MLXSW_I2C_CIR_STATUS_OFF 0x18 ++#define MLXSW_I2C_CIR2_OFF_STATUS (MLXSW_I2C_CIR2_BASE + \ ++ MLXSW_I2C_CIR_STATUS_OFF) ++#define MLXSW_I2C_OPMOD_SHIFT 12 ++#define MLXSW_I2C_GO_BIT_SHIFT 23 ++#define MLXSW_I2C_CIR_CTRL_STATUS_SHIFT 24 ++#define MLXSW_I2C_GO_BIT BIT(MLXSW_I2C_GO_BIT_SHIFT) ++#define MLXSW_I2C_GO_OPMODE BIT(MLXSW_I2C_OPMOD_SHIFT) ++#define MLXSW_I2C_SET_IMM_CMD (MLXSW_I2C_GO_OPMODE | \ ++ MLXSW_CMD_OPCODE_QUERY_FW) ++#define MLXSW_I2C_PUSH_IMM_CMD (MLXSW_I2C_GO_BIT | \ ++ MLXSW_I2C_SET_IMM_CMD) ++#define MLXSW_I2C_SET_CMD (MLXSW_CMD_OPCODE_ACCESS_REG) ++#define MLXSW_I2C_PUSH_CMD (MLXSW_I2C_GO_BIT | MLXSW_I2C_SET_CMD) ++#define MLXSW_I2C_TLV_HDR_SIZE 0x10 ++#define MLXSW_I2C_ADDR_WIDTH 4 ++#define MLXSW_I2C_PUSH_CMD_SIZE (MLXSW_I2C_ADDR_WIDTH + 4) ++#define MLXSW_I2C_READ_SEMA_SIZE 4 ++#define MLXSW_I2C_PREP_SIZE (MLXSW_I2C_ADDR_WIDTH + 28) ++#define MLXSW_I2C_MBOX_SIZE 20 ++#define MLXSW_I2C_MBOX_OUT_PARAM_OFF 12 ++#define MLXSW_I2C_MAX_BUFF_SIZE 32 ++#define MLXSW_I2C_MBOX_OFFSET_BITS 20 ++#define MLXSW_I2C_MBOX_SIZE_BITS 12 ++#define MLXSW_I2C_ADDR_BUF_SIZE 4 ++#define MLXSW_I2C_BLK_MAX 32 ++#define MLXSW_I2C_RETRY 5 ++#define MLXSW_I2C_TIMEOUT_MSECS 5000 ++ ++/** ++ * struct mlxsw_i2c - device private data: ++ * @cmd.mb_size_in: input mailbox size; ++ * @cmd.mb_off_in: input mailbox offset in register space; ++ * @cmd.mb_size_out: output mailbox size; ++ * @cmd.mb_off_out: output mailbox offset in register space; ++ * @cmd.lock: command execution lock; ++ * @dev: I2C device; ++ * @core: switch core pointer; ++ * @bus_info: bus info block; ++ */ ++struct mlxsw_i2c { ++ struct { ++ u32 mb_size_in; ++ u32 mb_off_in; ++ u32 mb_size_out; ++ u32 mb_off_out; ++ struct mutex lock; ++ } cmd; ++ struct device *dev; ++ struct mlxsw_core *core; ++ struct mlxsw_bus_info bus_info; ++}; ++ ++#define MLXSW_I2C_READ_MSG(_client, _addr_buf, _buf, _len) { \ ++ { .addr = (_client)->addr, \ ++ .buf = (_addr_buf), \ ++ .len = MLXSW_I2C_ADDR_BUF_SIZE, \ ++ .flags = 0 }, \ ++ { .addr = (_client)->addr, \ ++ .buf = (_buf), \ ++ .len = (_len), \ ++ .flags = I2C_M_RD } } ++ ++#define MLXSW_I2C_WRITE_MSG(_client, _buf, _len) \ ++ { .addr = (_client)->addr, \ ++ .buf = (u8 *)(_buf), \ ++ .len = (_len), \ ++ .flags = 0 } ++ ++/* Routine converts in and out mail boxes offset and size. */ ++static inline void ++mlxsw_i2c_convert_mbox(struct mlxsw_i2c *mlxsw_i2c, u8 *buf) ++{ ++ u32 tmp; ++ ++ /* Local in/out mailboxes: 20 bits for offset, 12 for size */ ++ tmp = be32_to_cpup((__be32 *) buf); ++ mlxsw_i2c->cmd.mb_off_in = tmp & ++ GENMASK(MLXSW_I2C_MBOX_OFFSET_BITS - 1, 0); ++ mlxsw_i2c->cmd.mb_size_in = (tmp & GENMASK(31, ++ MLXSW_I2C_MBOX_OFFSET_BITS)) >> ++ MLXSW_I2C_MBOX_OFFSET_BITS; ++ ++ tmp = be32_to_cpup((__be32 *) (buf + MLXSW_I2C_ADDR_WIDTH)); ++ mlxsw_i2c->cmd.mb_off_out = tmp & ++ GENMASK(MLXSW_I2C_MBOX_OFFSET_BITS - 1, 0); ++ mlxsw_i2c->cmd.mb_size_out = (tmp & GENMASK(31, ++ MLXSW_I2C_MBOX_OFFSET_BITS)) >> ++ MLXSW_I2C_MBOX_OFFSET_BITS; ++} ++ ++/* Routine obtains register size from mail box buffer. */ ++static inline int mlxsw_i2c_get_reg_size(u8 *in_mbox) ++{ ++ u16 tmp = be16_to_cpup((__be16 *) (in_mbox + MLXSW_I2C_TLV_HDR_SIZE)); ++ ++ return (tmp & 0x7ff) * 4 + MLXSW_I2C_TLV_HDR_SIZE; ++} ++ ++/* Routine sets I2C device internal offset in the transaction buffer. */ ++static inline void mlxsw_i2c_set_slave_addr(u8 *buf, u32 off) ++{ ++ __be32 *val = (__be32 *) buf; ++ ++ *val = htonl(off); ++} ++ ++/* Routine waits until go bit is cleared. */ ++static int mlxsw_i2c_wait_go_bit(struct i2c_client *client, ++ struct mlxsw_i2c *mlxsw_i2c, u8 *p_status) ++{ ++ u8 addr_buf[MLXSW_I2C_ADDR_BUF_SIZE]; ++ u8 buf[MLXSW_I2C_READ_SEMA_SIZE]; ++ int len = MLXSW_I2C_READ_SEMA_SIZE; ++ struct i2c_msg read_sema[] = ++ MLXSW_I2C_READ_MSG(client, addr_buf, buf, len); ++ bool wait_done = false; ++ unsigned long end; ++ int i = 0, err; ++ ++ mlxsw_i2c_set_slave_addr(addr_buf, MLXSW_I2C_CIR2_OFF_STATUS); ++ ++ end = jiffies + msecs_to_jiffies(MLXSW_I2C_TIMEOUT_MSECS); ++ do { ++ u32 ctrl; ++ ++ err = i2c_transfer(client->adapter, read_sema, ++ ARRAY_SIZE(read_sema)); ++ ++ ctrl = be32_to_cpu(*(__be32 *) buf); ++ if (err == ARRAY_SIZE(read_sema)) { ++ if (!(ctrl & MLXSW_I2C_GO_BIT)) { ++ wait_done = true; ++ *p_status = ctrl >> ++ MLXSW_I2C_CIR_CTRL_STATUS_SHIFT; ++ break; ++ } ++ } ++ cond_resched(); ++ } while ((time_before(jiffies, end)) || (i++ < MLXSW_I2C_RETRY)); ++ ++ if (wait_done) { ++ if (*p_status) ++ err = -EIO; ++ } else { ++ return -ETIMEDOUT; ++ } ++ ++ return err > 0 ? 0 : err; ++} ++ ++/* Routine posts a command to ASIC though mail box. */ ++static int mlxsw_i2c_write_cmd(struct i2c_client *client, ++ struct mlxsw_i2c *mlxsw_i2c, ++ int immediate) ++{ ++ __be32 push_cmd_buf[MLXSW_I2C_PUSH_CMD_SIZE / 4] = { ++ 0, cpu_to_be32(MLXSW_I2C_PUSH_IMM_CMD) ++ }; ++ __be32 prep_cmd_buf[MLXSW_I2C_PREP_SIZE / 4] = { ++ 0, 0, 0, 0, 0, 0, ++ cpu_to_be32(client->adapter->nr & 0xffff), ++ cpu_to_be32(MLXSW_I2C_SET_IMM_CMD) ++ }; ++ struct i2c_msg push_cmd = ++ MLXSW_I2C_WRITE_MSG(client, push_cmd_buf, ++ MLXSW_I2C_PUSH_CMD_SIZE); ++ struct i2c_msg prep_cmd = ++ MLXSW_I2C_WRITE_MSG(client, prep_cmd_buf, MLXSW_I2C_PREP_SIZE); ++ int err; ++ ++ if (!immediate) { ++ push_cmd_buf[1] = cpu_to_be32(MLXSW_I2C_PUSH_CMD); ++ prep_cmd_buf[7] = cpu_to_be32(MLXSW_I2C_SET_CMD); ++ } ++ mlxsw_i2c_set_slave_addr((u8 *)prep_cmd_buf, ++ MLXSW_I2C_CIR2_BASE); ++ mlxsw_i2c_set_slave_addr((u8 *)push_cmd_buf, ++ MLXSW_I2C_CIR2_OFF_STATUS); ++ ++ /* Prepare Command Interface Register for transaction */ ++ err = i2c_transfer(client->adapter, &prep_cmd, 1); ++ if (err < 0) ++ return err; ++ else if (err != 1) ++ return -EIO; ++ ++ /* Write out Command Interface Register GO bit to push transaction */ ++ err = i2c_transfer(client->adapter, &push_cmd, 1); ++ if (err < 0) ++ return err; ++ else if (err != 1) ++ return -EIO; ++ ++ return 0; ++} ++ ++/* Routine obtains mail box offsets from ASIC register space. */ ++static int mlxsw_i2c_get_mbox(struct i2c_client *client, ++ struct mlxsw_i2c *mlxsw_i2c) ++{ ++ u8 addr_buf[MLXSW_I2C_ADDR_BUF_SIZE]; ++ u8 buf[MLXSW_I2C_MBOX_SIZE]; ++ struct i2c_msg mbox_cmd[] = ++ MLXSW_I2C_READ_MSG(client, addr_buf, buf, MLXSW_I2C_MBOX_SIZE); ++ int err; ++ ++ /* Read mail boxes offsets. */ ++ mlxsw_i2c_set_slave_addr(addr_buf, MLXSW_I2C_CIR2_BASE); ++ err = i2c_transfer(client->adapter, mbox_cmd, 2); ++ if (err != 2) { ++ dev_err(&client->dev, "Could not obtain mail boxes\n"); ++ if (!err) ++ return -EIO; ++ else ++ return err; ++ } ++ ++ /* Convert mail boxes. */ ++ mlxsw_i2c_convert_mbox(mlxsw_i2c, &buf[MLXSW_I2C_MBOX_OUT_PARAM_OFF]); ++ ++ return err; ++} ++ ++/* Routine sends I2C write transaction to ASIC device. */ ++static int ++mlxsw_i2c_write(struct device *dev, size_t in_mbox_size, u8 *in_mbox, int num, ++ u8 *p_status) ++{ ++ struct i2c_client *client = to_i2c_client(dev); ++ struct mlxsw_i2c *mlxsw_i2c = i2c_get_clientdata(client); ++ unsigned long timeout = msecs_to_jiffies(MLXSW_I2C_TIMEOUT_MSECS); ++ u8 tran_buf[MLXSW_I2C_MAX_BUFF_SIZE + MLXSW_I2C_ADDR_BUF_SIZE]; ++ int off = mlxsw_i2c->cmd.mb_off_in, chunk_size, i, j; ++ unsigned long end; ++ struct i2c_msg write_tran = ++ MLXSW_I2C_WRITE_MSG(client, tran_buf, MLXSW_I2C_PUSH_CMD_SIZE); ++ int err; ++ ++ for (i = 0; i < num; i++) { ++ chunk_size = (in_mbox_size > MLXSW_I2C_BLK_MAX) ? ++ MLXSW_I2C_BLK_MAX : in_mbox_size; ++ write_tran.len = MLXSW_I2C_ADDR_WIDTH + chunk_size; ++ mlxsw_i2c_set_slave_addr(tran_buf, off); ++ memcpy(&tran_buf[MLXSW_I2C_ADDR_BUF_SIZE], in_mbox + ++ MLXSW_I2C_BLK_MAX * i, chunk_size); ++ ++ j = 0; ++ end = jiffies + timeout; ++ do { ++ err = i2c_transfer(client->adapter, &write_tran, 1); ++ if (err == 1) ++ break; ++ ++ cond_resched(); ++ } while ((time_before(jiffies, end)) || ++ (j++ < MLXSW_I2C_RETRY)); ++ ++ if (err != 1) { ++ if (!err) ++ err = -EIO; ++ return err; ++ } ++ ++ off += chunk_size; ++ in_mbox_size -= chunk_size; ++ } ++ ++ /* Prepare and write out Command Interface Register for transaction. */ ++ err = mlxsw_i2c_write_cmd(client, mlxsw_i2c, 0); ++ if (err) { ++ dev_err(&client->dev, "Could not start transaction"); ++ return -EIO; ++ } ++ ++ /* Wait until go bit is cleared. */ ++ err = mlxsw_i2c_wait_go_bit(client, mlxsw_i2c, p_status); ++ if (err) { ++ dev_err(&client->dev, "HW semaphore is not released"); ++ return err; ++ } ++ ++ /* Validate transaction completion status. */ ++ if (*p_status) { ++ dev_err(&client->dev, "Bad transaction completion status %x\n", ++ *p_status); ++ return -EIO; ++ } ++ ++ return 0; ++} ++ ++/* Routine executes I2C command. */ ++static int ++mlxsw_i2c_cmd(struct device *dev, size_t in_mbox_size, u8 *in_mbox, ++ size_t out_mbox_size, u8 *out_mbox, u8 *status) ++{ ++ struct i2c_client *client = to_i2c_client(dev); ++ struct mlxsw_i2c *mlxsw_i2c = i2c_get_clientdata(client); ++ unsigned long timeout = msecs_to_jiffies(MLXSW_I2C_TIMEOUT_MSECS); ++ u8 tran_buf[MLXSW_I2C_ADDR_BUF_SIZE]; ++ int num, chunk_size, reg_size, i, j; ++ int off = mlxsw_i2c->cmd.mb_off_out; ++ unsigned long end; ++ struct i2c_msg read_tran[] = ++ MLXSW_I2C_READ_MSG(client, tran_buf, NULL, 0); ++ int err; ++ ++ WARN_ON(in_mbox_size % sizeof(u32) || out_mbox_size % sizeof(u32)); ++ ++ reg_size = mlxsw_i2c_get_reg_size(in_mbox); ++ num = reg_size / MLXSW_I2C_BLK_MAX; ++ if (reg_size % MLXSW_I2C_BLK_MAX) ++ num++; ++ ++ if (mutex_lock_interruptible(&mlxsw_i2c->cmd.lock) < 0) { ++ dev_err(&client->dev, "Could not acquire lock"); ++ return -EINVAL; ++ } ++ ++ err = mlxsw_i2c_write(dev, reg_size, in_mbox, num, status); ++ if (err) ++ goto cmd_fail; ++ ++ /* No out mailbox is case of write transaction. */ ++ if (!out_mbox) { ++ mutex_unlock(&mlxsw_i2c->cmd.lock); ++ return 0; ++ } ++ ++ /* Send read transaction to get output mailbox content. */ ++ read_tran[1].buf = out_mbox; ++ for (i = 0; i < num; i++) { ++ chunk_size = (reg_size > MLXSW_I2C_BLK_MAX) ? ++ MLXSW_I2C_BLK_MAX : reg_size; ++ read_tran[1].len = chunk_size; ++ mlxsw_i2c_set_slave_addr(tran_buf, off); ++ ++ j = 0; ++ end = jiffies + timeout; ++ do { ++ err = i2c_transfer(client->adapter, read_tran, ++ ARRAY_SIZE(read_tran)); ++ if (err == ARRAY_SIZE(read_tran)) ++ break; ++ ++ cond_resched(); ++ } while ((time_before(jiffies, end)) || ++ (j++ < MLXSW_I2C_RETRY)); ++ ++ if (err != ARRAY_SIZE(read_tran)) { ++ if (!err) ++ err = -EIO; ++ ++ goto cmd_fail; ++ } ++ ++ off += chunk_size; ++ reg_size -= chunk_size; ++ read_tran[1].buf += chunk_size; ++ } ++ ++ mutex_unlock(&mlxsw_i2c->cmd.lock); ++ ++ return 0; ++ ++cmd_fail: ++ mutex_unlock(&mlxsw_i2c->cmd.lock); ++ return err; ++} ++ ++static int mlxsw_i2c_cmd_exec(void *bus_priv, u16 opcode, u8 opcode_mod, ++ u32 in_mod, bool out_mbox_direct, ++ char *in_mbox, size_t in_mbox_size, ++ char *out_mbox, size_t out_mbox_size, ++ u8 *status) ++{ ++ struct mlxsw_i2c *mlxsw_i2c = bus_priv; ++ ++ return mlxsw_i2c_cmd(mlxsw_i2c->dev, in_mbox_size, in_mbox, ++ out_mbox_size, out_mbox, status); ++} ++ ++static bool mlxsw_i2c_skb_transmit_busy(void *bus_priv, ++ const struct mlxsw_tx_info *tx_info) ++{ ++ return false; ++} ++ ++static int mlxsw_i2c_skb_transmit(void *bus_priv, struct sk_buff *skb, ++ const struct mlxsw_tx_info *tx_info) ++{ ++ return 0; ++} ++ ++static int ++mlxsw_i2c_init(void *bus_priv, struct mlxsw_core *mlxsw_core, ++ const struct mlxsw_config_profile *profile, ++ struct mlxsw_resources *resources) ++{ ++ struct mlxsw_i2c *mlxsw_i2c = bus_priv; ++ ++ mlxsw_i2c->core = mlxsw_core; ++ ++ return 0; ++} ++ ++static void mlxsw_i2c_fini(void *bus_priv) ++{ ++ struct mlxsw_i2c *mlxsw_i2c = bus_priv; ++ ++ mlxsw_i2c->core = NULL; ++} ++ ++static const struct mlxsw_bus mlxsw_i2c_bus = { ++ .kind = "i2c", ++ .init = mlxsw_i2c_init, ++ .fini = mlxsw_i2c_fini, ++ .skb_transmit_busy = mlxsw_i2c_skb_transmit_busy, ++ .skb_transmit = mlxsw_i2c_skb_transmit, ++ .cmd_exec = mlxsw_i2c_cmd_exec, ++}; ++ ++static int mlxsw_i2c_probe(struct i2c_client *client, ++ const struct i2c_device_id *id) ++{ ++ struct mlxsw_i2c *mlxsw_i2c; ++ u8 status; ++ int err; ++ ++ mlxsw_i2c = devm_kzalloc(&client->dev, sizeof(*mlxsw_i2c), GFP_KERNEL); ++ if (!mlxsw_i2c) ++ return -ENOMEM; ++ ++ i2c_set_clientdata(client, mlxsw_i2c); ++ mutex_init(&mlxsw_i2c->cmd.lock); ++ ++ /* In order to use mailboxes through the i2c, special area is reserved ++ * on the i2c address space that can be used for input and output ++ * mailboxes. Such mailboxes are called local mailboxes. When using a ++ * local mailbox, software should specify 0 as the Input/Output ++ * parameters. The location of the Local Mailbox addresses on the i2c ++ * space can be retrieved through the QUERY_FW command. ++ * For this purpose QUERY_FW is to be issued with opcode modifier equal ++ * 0x01. For such command the output parameter is an immediate value. ++ * Here QUERY_FW command is invoked for ASIC probing and for getting ++ * local mailboxes addresses from immedate output parameters. ++ */ ++ ++ /* Prepare and write out Command Interface Register for transaction */ ++ err = mlxsw_i2c_write_cmd(client, mlxsw_i2c, 1); ++ if (err) { ++ dev_err(&client->dev, "Could not start transaction"); ++ goto errout; ++ } ++ ++ /* Wait until go bit is cleared. */ ++ err = mlxsw_i2c_wait_go_bit(client, mlxsw_i2c, &status); ++ if (err) { ++ dev_err(&client->dev, "HW semaphore is not released"); ++ goto errout; ++ } ++ ++ /* Validate transaction completion status. */ ++ if (status) { ++ dev_err(&client->dev, "Bad transaction completion status %x\n", ++ status); ++ err = -EIO; ++ goto errout; ++ } ++ ++ /* Get mailbox offsets. */ ++ err = mlxsw_i2c_get_mbox(client, mlxsw_i2c); ++ if (err < 0) { ++ dev_err(&client->dev, "Fail to get mailboxes\n"); ++ goto errout; ++ } ++ ++ dev_info(&client->dev, "%s mb size=%x off=0x%08x out mb size=%x off=0x%08x\n", ++ id->name, mlxsw_i2c->cmd.mb_size_in, ++ mlxsw_i2c->cmd.mb_off_in, mlxsw_i2c->cmd.mb_size_out, ++ mlxsw_i2c->cmd.mb_off_out); ++ ++ /* Register device bus. */ ++ mlxsw_i2c->bus_info.device_kind = id->name; ++ mlxsw_i2c->bus_info.device_name = client->name; ++ mlxsw_i2c->bus_info.dev = &client->dev; ++ mlxsw_i2c->dev = &client->dev; ++ ++ err = mlxsw_core_bus_device_register(&mlxsw_i2c->bus_info, ++ &mlxsw_i2c_bus, mlxsw_i2c); ++ if (err) { ++ dev_err(&client->dev, "Fail to register core bus\n"); ++ return err; ++ } ++ ++ return 0; ++ ++errout: ++ i2c_set_clientdata(client, NULL); ++ ++ return err; ++} ++ ++static int mlxsw_i2c_remove(struct i2c_client *client) ++{ ++ struct mlxsw_i2c *mlxsw_i2c = i2c_get_clientdata(client); ++ ++ mlxsw_core_bus_device_unregister(mlxsw_i2c->core); ++ mutex_destroy(&mlxsw_i2c->cmd.lock); ++ ++ return 0; ++} ++ ++int mlxsw_i2c_driver_register(struct i2c_driver *i2c_driver) ++{ ++ i2c_driver->probe = mlxsw_i2c_probe; ++ i2c_driver->remove = mlxsw_i2c_remove; ++ return i2c_add_driver(i2c_driver); ++} ++EXPORT_SYMBOL(mlxsw_i2c_driver_register); ++ ++void mlxsw_i2c_driver_unregister(struct i2c_driver *i2c_driver) ++{ ++ i2c_del_driver(i2c_driver); ++} ++EXPORT_SYMBOL(mlxsw_i2c_driver_unregister); ++ ++MODULE_AUTHOR("Vadim Pasternak "); ++MODULE_DESCRIPTION("Mellanox switch I2C interface driver"); ++MODULE_LICENSE("Dual BSD/GPL"); +diff -Nur a/drivers/net/ethernet/mellanox/mlxsw/i2c.h b/drivers/net/ethernet/mellanox/mlxsw/i2c.h +--- a/drivers/net/ethernet/mellanox/mlxsw/i2c.h 1970-01-01 00:00:00.000000000 +0000 ++++ b/drivers/net/ethernet/mellanox/mlxsw/i2c.h 2017-11-09 12:03:09.944785064 +0000 +@@ -0,0 +1,60 @@ ++/* ++ * drivers/net/ethernet/mellanox/mlxsw/i2c.h ++ * Copyright (c) 2016 Mellanox Technologies. All rights reserved. ++ * Copyright (c) 2016 Vadim Pasternak ++ * ++ * Redistribution and use in source and binary forms, with or without ++ * modification, are permitted provided that the following conditions are met: ++ * ++ * 1. Redistributions of source code must retain the above copyright ++ * notice, this list of conditions and the following disclaimer. ++ * 2. Redistributions in binary form must reproduce the above copyright ++ * notice, this list of conditions and the following disclaimer in the ++ * documentation and/or other materials provided with the distribution. ++ * 3. Neither the names of the copyright holders nor the names of its ++ * contributors may be used to endorse or promote products derived from ++ * this software without specific prior written permission. ++ * ++ * Alternatively, this software may be distributed under the terms of the ++ * GNU General Public License ("GPL") version 2 as published by the Free ++ * Software Foundation. ++ * ++ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" ++ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE ++ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ++ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE ++ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR ++ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF ++ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS ++ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN ++ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ++ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE ++ * POSSIBILITY OF SUCH DAMAGE. ++ */ ++ ++#ifndef _MLXSW_I2C_H ++#define _MLXSW_I2C_H ++ ++#include ++ ++#if IS_ENABLED(CONFIG_MLXSW_I2C) ++ ++int mlxsw_i2c_driver_register(struct i2c_driver *i2c_driver); ++void mlxsw_i2c_driver_unregister(struct i2c_driver *i2c_driver); ++ ++#else ++ ++static inline int ++mlxsw_i2c_driver_register(struct i2c_driver *i2c_driver) ++{ ++ return -ENODEV; ++} ++ ++static inline void ++mlxsw_i2c_driver_unregister(struct i2c_driver *i2c_driver) ++{ ++} ++ ++#endif ++ ++#endif +diff -Nur a/drivers/net/ethernet/mellanox/mlxsw/minimal.c b/drivers/net/ethernet/mellanox/mlxsw/minimal.c +--- a/drivers/net/ethernet/mellanox/mlxsw/minimal.c 1970-01-01 00:00:00.000000000 +0000 ++++ b/drivers/net/ethernet/mellanox/mlxsw/minimal.c 2017-11-09 12:03:23.332785242 +0000 +@@ -0,0 +1,97 @@ ++/* ++ * drivers/net/ethernet/mellanox/mlxsw/minimal.c ++ * Copyright (c) 2016 Mellanox Technologies. All rights reserved. ++ * Copyright (c) 2016 Vadim Pasternak ++ * ++ * Redistribution and use in source and binary forms, with or without ++ * modification, are permitted provided that the following conditions are met: ++ * ++ * 1. Redistributions of source code must retain the above copyright ++ * notice, this list of conditions and the following disclaimer. ++ * 2. Redistributions in binary form must reproduce the above copyright ++ * notice, this list of conditions and the following disclaimer in the ++ * documentation and/or other materials provided with the distribution. ++ * 3. Neither the names of the copyright holders nor the names of its ++ * contributors may be used to endorse or promote products derived from ++ * this software without specific prior written permission. ++ * ++ * Alternatively, this software may be distributed under the terms of the ++ * GNU General Public License ("GPL") version 2 as published by the Free ++ * Software Foundation. ++ * ++ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" ++ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE ++ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ++ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE ++ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR ++ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF ++ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS ++ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN ++ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ++ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE ++ * POSSIBILITY OF SUCH DAMAGE. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++ ++#include "core.h" ++#include "i2c.h" ++ ++static const char mlxsw_minimal_driver_name[] = "mlxsw_minimal"; ++ ++static const struct mlxsw_config_profile mlxsw_minimal_config_profile; ++ ++static struct mlxsw_driver mlxsw_minimal_driver = { ++ .kind = mlxsw_minimal_driver_name, ++ .priv_size = 1, ++ .profile = &mlxsw_minimal_config_profile, ++}; ++ ++static const struct i2c_device_id mlxsw_minimal_i2c_id[] = { ++ { "mlxsw_minimal", 0}, ++ { }, ++}; ++ ++static struct i2c_driver mlxsw_minimal_i2c_driver = { ++ .driver.name = "mlxsw_minimal", ++ .class = I2C_CLASS_HWMON, ++ .id_table = mlxsw_minimal_i2c_id, ++}; ++ ++static int __init mlxsw_minimal_module_init(void) ++{ ++ int err; ++ ++ err = mlxsw_core_driver_register(&mlxsw_minimal_driver); ++ if (err) ++ return err; ++ ++ err = mlxsw_i2c_driver_register(&mlxsw_minimal_i2c_driver); ++ if (err) ++ goto err_i2c_driver_register; ++ ++ return 0; ++ ++err_i2c_driver_register: ++ mlxsw_core_driver_unregister(&mlxsw_minimal_driver); ++ ++ return err; ++} ++ ++static void __exit mlxsw_minimal_module_exit(void) ++{ ++ mlxsw_i2c_driver_unregister(&mlxsw_minimal_i2c_driver); ++ mlxsw_core_driver_unregister(&mlxsw_minimal_driver); ++} ++ ++module_init(mlxsw_minimal_module_init); ++module_exit(mlxsw_minimal_module_exit); ++ ++MODULE_LICENSE("Dual BSD/GPL"); ++MODULE_AUTHOR("Vadim Pasternak "); ++MODULE_DESCRIPTION("Mellanox minimal driver"); ++MODULE_DEVICE_TABLE(i2c, mlxsw_minimal_i2c_id); +diff -Nur a/drivers/net/ethernet/mellanox/mlxsw/pci.c b/drivers/net/ethernet/mellanox/mlxsw/pci.c +--- a/drivers/net/ethernet/mellanox/mlxsw/pci.c 2017-05-25 13:45:05.000000000 +0000 ++++ b/drivers/net/ethernet/mellanox/mlxsw/pci.c 2017-11-09 13:03:45.824833341 +0000 +@@ -1836,6 +1836,7 @@ + .skb_transmit_busy = mlxsw_pci_skb_transmit_busy, + .skb_transmit = mlxsw_pci_skb_transmit, + .cmd_exec = mlxsw_pci_cmd_exec, ++ .features = MLXSW_BUS_F_TXRX, + }; + + static int mlxsw_pci_sw_reset(struct mlxsw_pci *mlxsw_pci, +diff -Nur a/drivers/net/ethernet/mellanox/mlxsw/qsfp_sysfs.c b/drivers/net/ethernet/mellanox/mlxsw/qsfp_sysfs.c +--- a/drivers/net/ethernet/mellanox/mlxsw/qsfp_sysfs.c 1970-01-01 00:00:00.000000000 +0000 ++++ b/drivers/net/ethernet/mellanox/mlxsw/qsfp_sysfs.c 2017-11-09 13:04:52.192834223 +0000 +@@ -0,0 +1,379 @@ ++/* ++ * drivers/net/ethernet/mellanox/mlxsw/qsfp_sysfs.c ++ * Copyright (c) 2017 Mellanox Technologies. All rights reserved. ++ * Copyright (c) 2017 Vadim Pasternak ++ * ++ * Redistribution and use in source and binary forms, with or without ++ * modification, are permitted provided that the following conditions are met: ++ * ++ * 1. Redistributions of source code must retain the above copyright ++ * notice, this list of conditions and the following disclaimer. ++ * 2. Redistributions in binary form must reproduce the above copyright ++ * notice, this list of conditions and the following disclaimer in the ++ * documentation and/or other materials provided with the distribution. ++ * 3. Neither the names of the copyright holders nor the names of its ++ * contributors may be used to endorse or promote products derived from ++ * this software without specific prior written permission. ++ * ++ * Alternatively, this software may be distributed under the terms of the ++ * GNU General Public License ("GPL") version 2 as published by the Free ++ * Software Foundation. ++ * ++ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" ++ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE ++ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ++ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE ++ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR ++ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF ++ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS ++ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN ++ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ++ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE ++ * POSSIBILITY OF SUCH DAMAGE. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++ ++#include "core.h" ++ ++#define MLXSW_QSFP_I2C_ADDR 0x50 ++#define MLXSW_QSFP_PAGE_NUM 5 ++#define MLXSW_QSFP_PAGE_SIZE 128 ++#define MLXSW_QSFP_SUB_PAGE_NUM 3 ++#define MLXSW_QSFP_SUB_PAGE_SIZE 48 ++#define MLXSW_QSFP_LAST_SUB_PAGE_SIZE 32 ++#define MLXSW_QSFP_MAX_NUM 64 ++#define MLXSW_QSFP_MIN_REQ_LEN 4 ++#define MLXSW_QSFP_STATUS_VALID_TIME (120 * HZ) ++#define MLXSW_QSFP_MAX_CPLD_NUM 1 ++ ++static const u8 mlxsw_qsfp_page_number[] = { 0xa0, 0x00, 0x01, 0x02, 0x03 }; ++static const u16 mlxsw_qsfp_page_shift[] = { 0x00, 0x80, 0x80, 0x80, 0x80 }; ++ ++/** ++ * Mellanox device Management Cable Info Access Register buffer for reading ++ * QSFP EEPROM info is limited by 48 bytes. In case full page is to be read ++ * (128 bytes), such request will be implemented by three transactions of size ++ * 48, 48, 32. ++ */ ++static const u16 mlxsw_qsfp_sub_page_size[] = { ++ MLXSW_QSFP_SUB_PAGE_SIZE, ++ MLXSW_QSFP_SUB_PAGE_SIZE, ++ MLXSW_QSFP_LAST_SUB_PAGE_SIZE ++}; ++ ++struct mlxsw_qsfp_module { ++ unsigned long last_updated; ++ u8 cache_status; ++}; ++ ++struct mlxsw_qsfp { ++ struct mlxsw_core *core; ++ const struct mlxsw_bus_info *bus_info; ++ struct attribute *attrs[MLXSW_QSFP_MAX_NUM + 1]; ++ struct device_attribute *dev_attrs; ++ struct bin_attribute *eeprom; ++ struct bin_attribute **eeprom_attr_list; ++ struct mlxsw_qsfp_module modules[MLXSW_QSFP_MAX_NUM]; ++ u8 module_ind[MLXSW_QSFP_MAX_NUM]; ++ u8 module_count; ++ struct attribute *cpld_attrs[MLXSW_QSFP_MAX_CPLD_NUM + 1]; ++ struct device_attribute *cpld_dev_attrs; ++}; ++ ++static int ++mlxsw_qsfp_query_module_eeprom(struct mlxsw_qsfp *mlxsw_qsfp, u8 index, ++ loff_t off, size_t count, int page, char *buf) ++{ ++ char eeprom_tmp[MLXSW_QSFP_PAGE_SIZE]; ++ char mcia_pl[MLXSW_REG_MCIA_LEN]; ++ int status; ++ int err; ++ ++ mlxsw_reg_mcia_pack(mcia_pl, index, 0, page, off, count, ++ MLXSW_QSFP_I2C_ADDR); ++ ++ err = mlxsw_reg_query(mlxsw_qsfp->core, MLXSW_REG(mcia), mcia_pl); ++ if (err) ++ return err; ++ ++ status = mlxsw_reg_mcia_status_get(mcia_pl); ++ if (status) ++ return -EIO; ++ ++ mlxsw_reg_mcia_eeprom_memcpy_from(mcia_pl, eeprom_tmp); ++ memcpy(buf, eeprom_tmp, count); ++ ++ return 0; ++} ++ ++static int ++mlxsw_qsfp_get_module_eeprom(struct mlxsw_qsfp *mlxsw_qsfp, u8 index, ++ char *buf, loff_t off, size_t count) ++{ ++ int page_ind, page, page_off, subpage, offset, size, res = 0; ++ int err; ++ ++ if (!count) ++ return -EINVAL; ++ ++ memset(buf, 0, count); ++ size = count; ++ while (res < count) { ++ page_ind = off / MLXSW_QSFP_PAGE_SIZE; ++ page_off = off % MLXSW_QSFP_PAGE_SIZE; ++ page = mlxsw_qsfp_page_number[page_ind]; ++ offset = mlxsw_qsfp_page_shift[page_ind] + page_off; ++ subpage = page_off / MLXSW_QSFP_SUB_PAGE_SIZE; ++ size = min_t(u16, size, mlxsw_qsfp_sub_page_size[subpage]); ++ err = mlxsw_qsfp_query_module_eeprom(mlxsw_qsfp, index, offset, ++ size, page, buf + res); ++ if (err) { ++ dev_err(mlxsw_qsfp->bus_info->dev, "Eeprom query failed\n"); ++ return err; ++ } ++ off += size; ++ res += size; ++ size = count - size; ++ } ++ ++ return res; ++} ++ ++static ssize_t mlxsw_qsfp_bin_read(struct file *filp, struct kobject *kobj, ++ struct bin_attribute *attr, char *buf, ++ loff_t off, size_t count) ++{ ++ struct mlxsw_qsfp *mlxsw_qsfp = dev_get_platdata(container_of(kobj, ++ struct device, kobj)); ++ u8 *module_ind = attr->private; ++ size_t size; ++ ++ size = mlxsw_qsfp->eeprom[*module_ind].size; ++ ++ if (off > size) ++ return -ESPIPE; ++ else if (off == size) ++ return 0; ++ else if ((off + count) > size) ++ count = size - off; ++ ++ return mlxsw_qsfp_get_module_eeprom(mlxsw_qsfp, *module_ind, buf, off, ++ count); ++} ++ ++static ssize_t ++mlxsw_qsfp_status_show(struct device *dev, struct device_attribute *attr, ++ char *buf) ++{ ++ struct mlxsw_qsfp *mlxsw_qsfp = dev_get_platdata(dev); ++ char mcia_pl[MLXSW_REG_MCIA_LEN]; ++ int status; ++ u32 i; ++ int err; ++ ++ for (i = 0; i < mlxsw_qsfp->module_count; i++) { ++ if ((mlxsw_qsfp->dev_attrs + i) == attr) ++ break; ++ } ++ if (i == mlxsw_qsfp->module_count) ++ return -EINVAL; ++ ++ if (time_before(jiffies, mlxsw_qsfp->modules[i].last_updated + ++ MLXSW_QSFP_STATUS_VALID_TIME)) ++ return sprintf(buf, "%u\n", ++ mlxsw_qsfp->modules[i].cache_status); ++ ++ mlxsw_reg_mcia_pack(mcia_pl, i, 0, 0, 0, MLXSW_QSFP_MIN_REQ_LEN, ++ MLXSW_QSFP_I2C_ADDR); ++ err = mlxsw_reg_query(mlxsw_qsfp->core, MLXSW_REG(mcia), mcia_pl); ++ if (err) ++ return err; ++ ++ status = mlxsw_reg_mcia_status_get(mcia_pl); ++ mlxsw_qsfp->modules[i].cache_status = !status; ++ mlxsw_qsfp->modules[i].last_updated = jiffies; ++ ++ return sprintf(buf, "%u\n", !status); ++} ++ ++static ssize_t ++mlxsw_qsfp_cpld_show(struct device *dev, struct device_attribute *attr, ++ char *buf) ++{ ++ struct mlxsw_qsfp *mlxsw_qsfp = dev_get_platdata(dev); ++ char msci_pl[MLXSW_REG_MSCI_LEN]; ++ u32 version, i; ++ int err; ++ ++ for (i = 0; i < MLXSW_QSFP_MAX_CPLD_NUM; i++) { ++ if ((mlxsw_qsfp->cpld_dev_attrs + i) == attr) ++ break; ++ } ++ if (i == MLXSW_QSFP_MAX_CPLD_NUM) ++ return -EINVAL; ++ ++ mlxsw_reg_msci_pack(msci_pl, i); ++ err = mlxsw_reg_query(mlxsw_qsfp->core, MLXSW_REG(msci), msci_pl); ++ if (err) ++ return err; ++ ++ version = mlxsw_reg_msci_version_get(msci_pl); ++ ++ return sprintf(buf, "%u\n", version); ++} ++ ++int mlxsw_qsfp_init(struct mlxsw_core *mlxsw_core, ++ const struct mlxsw_bus_info *mlxsw_bus_info, ++ struct mlxsw_qsfp **p_qsfp) ++{ ++ struct device_attribute *dev_attr, *cpld_dev_attr; ++ char pmlp_pl[MLXSW_REG_PMLP_LEN]; ++ struct mlxsw_qsfp *mlxsw_qsfp; ++ struct bin_attribute *eeprom; ++ int i, count; ++ u8 width; ++ int err; ++ ++ if (!strcmp(mlxsw_bus_info->device_kind, "i2c")) ++ return 0; ++ ++ mlxsw_qsfp = devm_kzalloc(mlxsw_bus_info->dev, sizeof(*mlxsw_qsfp), ++ GFP_KERNEL); ++ if (!mlxsw_qsfp) ++ return -ENOMEM; ++ ++ mlxsw_qsfp->core = mlxsw_core; ++ mlxsw_qsfp->bus_info = mlxsw_bus_info; ++ mlxsw_bus_info->dev->platform_data = mlxsw_qsfp; ++ ++ for (i = 1; i <= MLXSW_QSFP_MAX_NUM; i++) { ++ mlxsw_reg_pmlp_pack(pmlp_pl, i); ++ err = mlxsw_reg_query(mlxsw_qsfp->core, MLXSW_REG(pmlp), ++ pmlp_pl); ++ if (err) ++ return err; ++ width = mlxsw_reg_pmlp_width_get(pmlp_pl); ++ if (!width) ++ continue; ++ mlxsw_qsfp->module_count++; ++ } ++ ++ count = mlxsw_qsfp->module_count + 1; ++ mlxsw_qsfp->eeprom = devm_kzalloc(mlxsw_bus_info->dev, ++ mlxsw_qsfp->module_count * ++ sizeof(*mlxsw_qsfp->eeprom), ++ GFP_KERNEL); ++ if (!mlxsw_qsfp->eeprom) ++ return -ENOMEM; ++ ++ mlxsw_qsfp->eeprom_attr_list = devm_kzalloc(mlxsw_bus_info->dev, ++ count * ++ sizeof(mlxsw_qsfp->eeprom), ++ GFP_KERNEL); ++ if (!mlxsw_qsfp->eeprom_attr_list) ++ return -ENOMEM; ++ ++ mlxsw_qsfp->dev_attrs = devm_kzalloc(mlxsw_bus_info->dev, count * ++ sizeof(*mlxsw_qsfp->dev_attrs), ++ GFP_KERNEL); ++ if (!mlxsw_qsfp->dev_attrs) ++ return -ENOMEM; ++ ++ mlxsw_qsfp->cpld_dev_attrs = devm_kzalloc(mlxsw_bus_info->dev, ++ MLXSW_QSFP_MAX_CPLD_NUM * ++ sizeof(*mlxsw_qsfp->cpld_dev_attrs), ++ GFP_KERNEL); ++ if (!mlxsw_qsfp->cpld_dev_attrs) ++ return -ENOMEM; ++ ++ eeprom = mlxsw_qsfp->eeprom; ++ dev_attr = mlxsw_qsfp->dev_attrs; ++ for (i = 0; i < mlxsw_qsfp->module_count; i++, eeprom++, dev_attr++) { ++ dev_attr->show = mlxsw_qsfp_status_show; ++ dev_attr->attr.mode = 0444; ++ dev_attr->attr.name = devm_kasprintf(mlxsw_bus_info->dev, ++ GFP_KERNEL, ++ "qsfp%d_status", i + 1); ++ mlxsw_qsfp->attrs[i] = &dev_attr->attr; ++ sysfs_attr_init(&dev_attr->attr); ++ err = sysfs_create_file(&mlxsw_bus_info->dev->kobj, ++ mlxsw_qsfp->attrs[i]); ++ if (err) ++ goto err_create_file; ++ ++ sysfs_bin_attr_init(eeprom); ++ eeprom->attr.name = devm_kasprintf(mlxsw_bus_info->dev, ++ GFP_KERNEL, "qsfp%d", ++ i + 1); ++ eeprom->attr.mode = 0444; ++ eeprom->read = mlxsw_qsfp_bin_read; ++ eeprom->size = MLXSW_QSFP_PAGE_NUM * MLXSW_QSFP_PAGE_SIZE; ++ mlxsw_qsfp->module_ind[i] = i; ++ eeprom->private = &mlxsw_qsfp->module_ind[i]; ++ mlxsw_qsfp->eeprom_attr_list[i] = eeprom; ++ err = sysfs_create_bin_file(&mlxsw_bus_info->dev->kobj, ++ eeprom); ++ if (err) ++ goto err_create_bin_file; ++ } ++ ++ cpld_dev_attr = mlxsw_qsfp->cpld_dev_attrs; ++ for (i = 0; i < MLXSW_QSFP_MAX_CPLD_NUM; i++, cpld_dev_attr++) { ++ cpld_dev_attr->show = mlxsw_qsfp_cpld_show; ++ cpld_dev_attr->attr.mode = 0444; ++ cpld_dev_attr->attr.name = devm_kasprintf(mlxsw_bus_info->dev, ++ GFP_KERNEL, ++ "cpld%d_version", i + 1); ++ mlxsw_qsfp->cpld_attrs[i] = &cpld_dev_attr->attr; ++ sysfs_attr_init(&cpld_dev_attr->attr); ++ err = sysfs_create_file(&mlxsw_bus_info->dev->kobj, ++ mlxsw_qsfp->cpld_attrs[i]); ++ if (err) ++ goto err_create_cpld_file; ++ } ++ ++ *p_qsfp = mlxsw_qsfp; ++ ++ return 0; ++ ++err_create_cpld_file: ++ sysfs_remove_file(&mlxsw_bus_info->dev->kobj, ++ mlxsw_qsfp->cpld_attrs[i--]); ++ i = mlxsw_qsfp->module_count; ++err_create_bin_file: ++ sysfs_remove_file(&mlxsw_bus_info->dev->kobj, ++ mlxsw_qsfp->attrs[i--]); ++err_create_file: ++ while (--i > 0) { ++ sysfs_remove_bin_file(&mlxsw_bus_info->dev->kobj, ++ mlxsw_qsfp->eeprom_attr_list[i]); ++ sysfs_remove_file(&mlxsw_bus_info->dev->kobj, ++ mlxsw_qsfp->attrs[i]); ++ } ++ ++ return err; ++} ++ ++void mlxsw_qsfp_fini(struct mlxsw_qsfp *mlxsw_qsfp) ++{ ++ int i; ++ ++ if (!strcmp(mlxsw_qsfp->bus_info->device_kind, "i2c")) ++ return; ++ ++ for (i = mlxsw_qsfp->module_count - 1; i >= 0; i--) { ++ sysfs_remove_bin_file(&mlxsw_qsfp->bus_info->dev->kobj, ++ mlxsw_qsfp->eeprom_attr_list[i]); ++ sysfs_remove_file(&mlxsw_qsfp->bus_info->dev->kobj, ++ mlxsw_qsfp->attrs[i]); ++ } ++} ++ ++MODULE_LICENSE("Dual BSD/GPL"); ++MODULE_AUTHOR("Vadim Pasternak "); ++MODULE_DESCRIPTION("Mellanox switch QSFP sysfs driver"); +diff -Nur a/drivers/net/ethernet/mellanox/mlxsw/reg.h b/drivers/net/ethernet/mellanox/mlxsw/reg.h +--- a/drivers/net/ethernet/mellanox/mlxsw/reg.h 2017-05-25 13:45:05.000000000 +0000 ++++ b/drivers/net/ethernet/mellanox/mlxsw/reg.h 2017-11-09 13:05:10.576834467 +0000 +@@ -50,6 +50,12 @@ + u16 len; /* In u8 */ + }; + ++#define MLXSW_REG_DEFINE(_name, _id, _len) \ ++static const struct mlxsw_reg_info mlxsw_reg_##_name = { \ ++ .id = _id, \ ++ .len = _len, \ ++} ++ + #define MLXSW_REG(type) (&mlxsw_reg_##type) + #define MLXSW_REG_LEN(type) MLXSW_REG(type)->len + #define MLXSW_REG_ZERO(type, payload) memset(payload, 0, MLXSW_REG(type)->len) +@@ -4466,7 +4472,7 @@ + */ + MLXSW_ITEM32(reg, mfcr, pwm_frequency, 0x00, 0, 6); + +-#define MLXSW_MFCR_TACHOS_MAX 10 ++#define MLXSW_MFCR_TACHOS_MAX 12 + + /* reg_mfcr_tacho_active + * Indicates which of the tachometer is active (bit per tachometer). +@@ -4564,6 +4570,54 @@ + mlxsw_reg_mfsm_tacho_set(payload, tacho); + } + ++/* MFSL - Management Fan Speed Limit Register ++ * ------------------------------------------ ++ * The Fan Speed Limit register is used to configure the fan speed ++ * event / interrupt notification mechanism. Fan speed threshold are ++ * defined for both under-speed and over-speed. ++ */ ++#define MLXSW_REG_MFSL_ID 0x9004 ++#define MLXSW_REG_MFSL_LEN 0x0C ++ ++MLXSW_REG_DEFINE(mfsl, MLXSW_REG_MFSL_ID, MLXSW_REG_MFSL_LEN); ++ ++/* reg_mfsl_tacho ++ * Fan tachometer index. ++ * Access: Index ++ */ ++MLXSW_ITEM32(reg, mfsl, tacho, 0x00, 24, 4); ++ ++/* reg_mfsl_tach_min ++ * Tachometer minimum value (minimum RPM). ++ * Access: RW ++ */ ++MLXSW_ITEM32(reg, mfsl, tach_min, 0x04, 0, 16); ++ ++/* reg_mfsl_tach_max ++ * Tachometer maximum value (maximum RPM). ++ * Access: RW ++ */ ++MLXSW_ITEM32(reg, mfsl, tach_max, 0x08, 0, 16); ++ ++static inline void mlxsw_reg_mfsl_pack(char *payload, u8 tacho, ++ u16 tach_min, u16 tach_max) ++{ ++ MLXSW_REG_ZERO(mfsl, payload); ++ mlxsw_reg_mfsl_tacho_set(payload, tacho); ++ mlxsw_reg_mfsl_tach_min_set(payload, tach_min); ++ mlxsw_reg_mfsl_tach_max_set(payload, tach_max); ++} ++ ++static inline void mlxsw_reg_mfsl_unpack(char *payload, u8 tacho, ++ u16 *p_tach_min, u16 *p_tach_max) ++{ ++ if (p_tach_min) ++ *p_tach_min = mlxsw_reg_mfsl_tach_min_get(payload); ++ ++ if (p_tach_max) ++ *p_tach_max = mlxsw_reg_mfsl_tach_max_get(payload); ++} ++ + /* MTCAP - Management Temperature Capabilities + * ------------------------------------------- + * This register exposes the capabilities of the device and +@@ -4635,6 +4689,29 @@ + */ + MLXSW_ITEM32(reg, mtmp, max_temperature, 0x08, 0, 16); + ++/* reg_mtmp_tee ++ * Temperature Event Enable. ++ * 0 - Do not generate event ++ * 1 - Generate event ++ * 2 - Generate single event ++ * Access: RW ++ */ ++MLXSW_ITEM32(reg, mtmp, tee, 0x0C, 30, 2); ++ ++#define MLXSW_REG_MTMP_THRESH_HI 0x348 /* 105 Celsius */ ++ ++/* reg_mtmp_temperature_threshold_hi ++ * High threshold for Temperature Warning Event. In 0.125 Celsius. ++ * Access: RW ++ */ ++MLXSW_ITEM32(reg, mtmp, temperature_threshold_hi, 0x0C, 0, 16); ++ ++/* reg_mtmp_temperature_threshold_lo ++ * Low threshold for Temperature Warning Event. In 0.125 Celsius. ++ * Access: RW ++ */ ++MLXSW_ITEM32(reg, mtmp, temperature_threshold_lo, 0x10, 0, 16); ++ + #define MLXSW_REG_MTMP_SENSOR_NAME_SIZE 8 + + /* reg_mtmp_sensor_name +@@ -4651,6 +4728,8 @@ + mlxsw_reg_mtmp_sensor_index_set(payload, sensor_index); + mlxsw_reg_mtmp_mte_set(payload, max_temp_enable); + mlxsw_reg_mtmp_mtr_set(payload, max_temp_reset); ++ mlxsw_reg_mtmp_temperature_threshold_hi_set(payload, ++ MLXSW_REG_MTMP_THRESH_HI); + } + + static inline void mlxsw_reg_mtmp_unpack(char *payload, unsigned int *p_temp, +@@ -4671,6 +4750,81 @@ + mlxsw_reg_mtmp_sensor_name_memcpy_from(payload, sensor_name); + } + ++/* MCIA - Management Cable Info Access ++ * ----------------------------------- ++ * MCIA register is used to access the SFP+ and QSFP connector's EPROM. ++ */ ++ ++#define MLXSW_REG_MCIA_ID 0x9014 ++#define MLXSW_REG_MCIA_LEN 0x40 ++ ++MLXSW_REG_DEFINE(mcia, MLXSW_REG_MCIA_ID, MLXSW_REG_MCIA_LEN); ++ ++/* reg_mcia_l ++ * Lock bit. Setting this bit will lock the access to the specific ++ * cable. Used for updating a full page in a cable EPROM. Any access ++ * other then subsequence writes will fail while the port is locked. ++ * Access: RW ++ */ ++MLXSW_ITEM32(reg, mcia, l, 0x00, 31, 1); ++ ++/* reg_mcia_module ++ * Module number. ++ * Access: Index ++ */ ++MLXSW_ITEM32(reg, mcia, module, 0x00, 16, 8); ++ ++/* reg_mcia_status ++ * Module status. ++ * Access: RO ++ */ ++MLXSW_ITEM32(reg, mcia, status, 0x00, 0, 8); ++ ++/* reg_mcia_i2c_device_address ++ * I2C device address. ++ * Access: RW ++ */ ++MLXSW_ITEM32(reg, mcia, i2c_device_address, 0x04, 24, 8); ++ ++/* reg_mcia_page_number ++ * Page number. ++ * Access: RW ++ */ ++MLXSW_ITEM32(reg, mcia, page_number, 0x04, 16, 8); ++ ++/* reg_mcia_device_address ++ * Device address. ++ * Access: RW ++ */ ++MLXSW_ITEM32(reg, mcia, device_address, 0x04, 0, 16); ++ ++/* reg_mcia_size ++ * Number of bytes to read/write (up to 48 bytes). ++ * Access: RW ++ */ ++MLXSW_ITEM32(reg, mcia, size, 0x08, 0, 16); ++ ++#define MLXSW_SP_REG_MCIA_EEPROM_SIZE 48 ++ ++/* reg_mcia_eeprom ++ * Bytes to read/write. ++ * Access: RW ++ */ ++MLXSW_ITEM_BUF(reg, mcia, eeprom, 0x10, MLXSW_SP_REG_MCIA_EEPROM_SIZE); ++ ++static inline void mlxsw_reg_mcia_pack(char *payload, u8 module, u8 lock, ++ u8 page_number, u16 device_addr, ++ u8 size, u8 i2c_device_addr) ++{ ++ MLXSW_REG_ZERO(mcia, payload); ++ mlxsw_reg_mcia_module_set(payload, module); ++ mlxsw_reg_mcia_l_set(payload, lock); ++ mlxsw_reg_mcia_page_number_set(payload, page_number); ++ mlxsw_reg_mcia_device_address_set(payload, device_addr); ++ mlxsw_reg_mcia_size_set(payload, size); ++ mlxsw_reg_mcia_i2c_device_address_set(payload, i2c_device_addr); ++} ++ + /* MPAT - Monitoring Port Analyzer Table + * ------------------------------------- + * MPAT Register is used to query and configure the Switch PortAnalyzer Table. +@@ -4788,6 +4942,43 @@ + mlxsw_reg_mpar_pa_id_set(payload, pa_id); + } + ++/* MSCI - Management System CPLD Information Register ++ * --------------------------------------------------- ++ * This register allows querying for the System CPLD(s) information. ++ */ ++#define MLXSW_REG_MSCI_ID 0x902A ++#define MLXSW_REG_MSCI_LEN 0x10 ++ ++static const struct mlxsw_reg_info mlxsw_reg_msci = { ++ .id = MLXSW_REG_MSCI_ID, ++ .len = MLXSW_REG_MSCI_LEN, ++}; ++ ++/* reg_msci_index ++ * Index to access. ++ * Access: Index ++ */ ++MLXSW_ITEM32(reg, msci, index, 0x00, 0, 4); ++ ++/* reg_msci_version ++ * CPLD version. ++ * Access: R0 ++ */ ++MLXSW_ITEM32(reg, msci, version, 0x04, 0, 32); ++ ++static inline void ++mlxsw_reg_msci_pack(char *payload, u8 index) ++{ ++ MLXSW_REG_ZERO(msci, payload); ++ mlxsw_reg_msci_index_set(payload, index); ++} ++ ++static inline void ++mlxsw_reg_msci_unpack(char *payload, u16 *p_version) ++{ ++ *p_version = mlxsw_reg_msci_version_get(payload); ++} ++ + /* MLCR - Management LED Control Register + * -------------------------------------- + * Controls the system LEDs. diff --git a/patch/0007-hwmon-pmbus-Add-support-for-Intel-VID-protocol-VR13.patch b/patch/0007-hwmon-pmbus-Add-support-for-Intel-VID-protocol-VR13.patch new file mode 100644 index 000000000..f0a9bde81 --- /dev/null +++ b/patch/0007-hwmon-pmbus-Add-support-for-Intel-VID-protocol-VR13.patch @@ -0,0 +1,30 @@ +Linux backport patch. Includes following commits: +a4dffccb72a7fa46bb0d7f29e607375387e09956 + + +diff -Nur a/drivers/hwmon/pmbus/pmbus.h b/drivers/hwmon/pmbus/pmbus.h +--- a/drivers/hwmon/pmbus/pmbus.h 2017-11-09 16:25:22.760993964 +0000 ++++ b/drivers/hwmon/pmbus/pmbus.h 2017-11-09 16:26:02.568994492 +0000 +@@ -341,7 +341,7 @@ + #define PMBUS_HAVE_STATUS_VMON BIT(19) + + enum pmbus_data_format { linear = 0, direct, vid }; +-enum vrm_version { vr11 = 0, vr12 }; ++enum vrm_version { vr11 = 0, vr12, vr13 }; + + struct pmbus_driver_info { + int pages; /* Total number of pages */ +diff -Nur a/drivers/hwmon/pmbus/pmbus_core.c b/drivers/hwmon/pmbus/pmbus_core.c +--- a/drivers/hwmon/pmbus/pmbus_core.c 2017-11-09 16:25:22.760993964 +0000 ++++ b/drivers/hwmon/pmbus/pmbus_core.c 2017-11-09 16:26:02.568994492 +0000 +@@ -531,6 +531,10 @@ + if (val >= 0x01) + rv = 250 + (val - 1) * 5; + break; ++ case vr13: ++ if (val >= 0x01) ++ rv = 500 + (val - 1) * 10; ++ break; + } + return rv; + } diff --git a/patch/0008-hwmon-pmbus-Add-support-for-Texas-Instruments-tps536.patch b/patch/0008-hwmon-pmbus-Add-support-for-Texas-Instruments-tps536.patch new file mode 100644 index 000000000..f95e27695 --- /dev/null +++ b/patch/0008-hwmon-pmbus-Add-support-for-Texas-Instruments-tps536.patch @@ -0,0 +1,151 @@ +Linux backport patch. Includes following commits: +f7caf758e26ab84b2b9def9ec68235c85d645597 + + +diff -Nur a/drivers/hwmon/pmbus/Kconfig b/drivers/hwmon/pmbus/Kconfig +--- a/drivers/hwmon/pmbus/Kconfig 2017-11-09 16:34:05.269000902 +0000 ++++ b/drivers/hwmon/pmbus/Kconfig 2017-11-09 16:35:49.701002288 +0000 +@@ -125,6 +125,15 @@ + This driver can also be built as a module. If so, the module will + be called tps40422. + ++config SENSORS_TPS53679 ++ tristate "TI TPS53679" ++ help ++ If you say yes here you get hardware monitoring support for TI ++ TPS53679. ++ ++ This driver can also be built as a module. If so, the module will ++ be called tps53679. ++ + config SENSORS_UCD9000 + tristate "TI UCD90120, UCD90124, UCD90160, UCD9090, UCD90910" + default n +diff -Nur a/drivers/hwmon/pmbus/Makefile b/drivers/hwmon/pmbus/Makefile +--- a/drivers/hwmon/pmbus/Makefile 2017-11-09 16:34:05.269000902 +0000 ++++ b/drivers/hwmon/pmbus/Makefile 2017-11-09 16:35:49.701002288 +0000 +@@ -13,6 +13,7 @@ + obj-$(CONFIG_SENSORS_MAX34440) += max34440.o + obj-$(CONFIG_SENSORS_MAX8688) += max8688.o + obj-$(CONFIG_SENSORS_TPS40422) += tps40422.o ++obj-$(CONFIG_SENSORS_TPS53679) += tps53679.o + obj-$(CONFIG_SENSORS_UCD9000) += ucd9000.o + obj-$(CONFIG_SENSORS_UCD9200) += ucd9200.o + obj-$(CONFIG_SENSORS_ZL6100) += zl6100.o +diff -Nur a/drivers/hwmon/pmbus/tps53679.c b/drivers/hwmon/pmbus/tps53679.c +--- a/drivers/hwmon/pmbus/tps53679.c 1970-01-01 00:00:00.000000000 +0000 ++++ b/drivers/hwmon/pmbus/tps53679.c 2017-11-09 16:35:49.701002288 +0000 +@@ -0,0 +1,113 @@ ++/* ++ * Hardware monitoring driver for Texas Instruments TPS53679 ++ * ++ * Copyright (c) 2017 Mellanox Technologies. All rights reserved. ++ * Copyright (c) 2017 Vadim Pasternak ++ * ++ * This program is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation; either version 2 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include "pmbus.h" ++ ++#define TPS53679_PROT_VR12_5MV 0x01 /* VR12.0 mode, 5-mV DAC */ ++#define TPS53679_PROT_VR12_5_10MV 0x02 /* VR12.5 mode, 10-mV DAC */ ++#define TPS53679_PROT_VR13_10MV 0x04 /* VR13.0 mode, 10-mV DAC */ ++#define TPS53679_PROT_IMVP8_5MV 0x05 /* IMVP8 mode, 5-mV DAC */ ++#define TPS53679_PROT_VR13_5MV 0x07 /* VR13.0 mode, 5-mV DAC */ ++#define TPS53679_PAGE_NUM 2 ++ ++static int tps53679_identify(struct i2c_client *client, ++ struct pmbus_driver_info *info) ++{ ++ u8 vout_params; ++ int ret; ++ ++ /* Read the register with VOUT scaling value.*/ ++ ret = pmbus_read_byte_data(client, 0, PMBUS_VOUT_MODE); ++ if (ret < 0) ++ return ret; ++ ++ vout_params = ret & GENMASK(4, 0); ++ ++ switch (vout_params) { ++ case TPS53679_PROT_VR13_10MV: ++ case TPS53679_PROT_VR12_5_10MV: ++ info->vrm_version = vr13; ++ break; ++ case TPS53679_PROT_VR13_5MV: ++ case TPS53679_PROT_VR12_5MV: ++ case TPS53679_PROT_IMVP8_5MV: ++ info->vrm_version = vr12; ++ break; ++ default: ++ return -EINVAL; ++ } ++ ++ return 0; ++} ++ ++static struct pmbus_driver_info tps53679_info = { ++ .pages = TPS53679_PAGE_NUM, ++ .format[PSC_VOLTAGE_IN] = linear, ++ .format[PSC_VOLTAGE_OUT] = vid, ++ .format[PSC_TEMPERATURE] = linear, ++ .format[PSC_CURRENT_OUT] = linear, ++ .format[PSC_POWER] = linear, ++ .func[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT | ++ PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT | ++ PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP | ++ PMBUS_HAVE_POUT, ++ .func[1] = PMBUS_HAVE_VIN | PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT | ++ PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT | ++ PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP | ++ PMBUS_HAVE_POUT, ++ .identify = tps53679_identify, ++}; ++ ++static int tps53679_probe(struct i2c_client *client, ++ const struct i2c_device_id *id) ++{ ++ return pmbus_do_probe(client, id, &tps53679_info); ++} ++ ++static const struct i2c_device_id tps53679_id[] = { ++ {"tps53679", 0}, ++ {} ++}; ++ ++MODULE_DEVICE_TABLE(i2c, tps53679_id); ++ ++static const struct of_device_id tps53679_of_match[] = { ++ {.compatible = "ti,tps53679"}, ++ {} ++}; ++MODULE_DEVICE_TABLE(of, tps53679_of_match); ++ ++static struct i2c_driver tps53679_driver = { ++ .driver = { ++ .name = "tps53679", ++ .of_match_table = of_match_ptr(tps53679_of_match), ++ }, ++ .probe = tps53679_probe, ++ .remove = pmbus_do_remove, ++ .id_table = tps53679_id, ++}; ++ ++module_i2c_driver(tps53679_driver); ++ ++MODULE_AUTHOR("Vadim Pasternak "); ++MODULE_DESCRIPTION("PMBus driver for Texas Instruments TPS53679"); ++MODULE_LICENSE("GPL"); diff --git a/patch/0009-platform-mellonox-introduce-mlxreg-io-driver-and-add.patch b/patch/0009-platform-mellonox-introduce-mlxreg-io-driver-and-add.patch new file mode 100644 index 000000000..db22b149b --- /dev/null +++ b/patch/0009-platform-mellonox-introduce-mlxreg-io-driver-and-add.patch @@ -0,0 +1,685 @@ +From 2c7476ab57dd42d8cba6c417ff32a77252964858 Mon Sep 17 00:00:00 2001 +From: Vadim Pasternak +Date: Thu, 16 Nov 2017 17:22:56 +0000 +Subject: [v4.9 backport 38/38] platform: mellonox: introduce mlxreg-io driver + and add driver activation to mlx-platform + +Patch introduces new module mlxreg-io, which exposes the registers of the +programmable devices, equipped on Melanox systems to sysfs. These are the +registers, which are used for system resets operation, system reset causes +monitoring, select operation and version info. + +Signed-off-by: Vadim Pasternak +--- + drivers/leds/leds-mlxreg.c | 10 +- + drivers/platform/mellanox/Kconfig | 11 ++ + drivers/platform/mellanox/Makefile | 1 + + drivers/platform/mellanox/mlxreg-io.c | 211 ++++++++++++++++++++++++++++++++++ + drivers/platform/x86/mlx-platform.c | 193 +++++++++++++++++++++++++++++-- + include/linux/platform_data/mlxreg.h | 6 +- + 6 files changed, 418 insertions(+), 14 deletions(-) + create mode 100644 drivers/platform/mellanox/mlxreg-io.c + +diff --git a/drivers/leds/leds-mlxreg.c b/drivers/leds/leds-mlxreg.c +index a932f20..036c214 100644 +--- a/drivers/leds/leds-mlxreg.c ++++ b/drivers/leds/leds-mlxreg.c +@@ -79,7 +79,7 @@ struct mlxreg_led_data { + */ + struct mlxreg_led_priv_data { + struct platform_device *pdev; +- struct mlxreg_core_led_platform_data *pdata; ++ struct mlxreg_core_platform_data *pdata; + struct mutex access_lock; /* protect IO operations */ + }; + +@@ -87,7 +87,7 @@ static int + mlxreg_led_store_hw(struct mlxreg_led_data *led_data, u8 vset) + { + struct mlxreg_led_priv_data *priv = led_data->data_parent; +- struct mlxreg_core_led_platform_data *led_pdata = priv->pdata; ++ struct mlxreg_core_platform_data *led_pdata = priv->pdata; + struct mlxreg_core_data *data = led_data->data; + u32 regval; + u32 nib; +@@ -125,7 +125,7 @@ static enum led_brightness + mlxreg_led_get_hw(struct mlxreg_led_data *led_data) + { + struct mlxreg_led_priv_data *priv = led_data->data_parent; +- struct mlxreg_core_led_platform_data *led_pdata = priv->pdata; ++ struct mlxreg_core_platform_data *led_pdata = priv->pdata; + struct mlxreg_core_data *data = led_data->data; + u32 regval; + int ret; +@@ -212,7 +212,7 @@ mlxreg_led_blink_set(struct led_classdev *cled, unsigned long *delay_on, + + static int mlxreg_led_config(struct mlxreg_led_priv_data *priv) + { +- struct mlxreg_core_led_platform_data *led_pdata = priv->pdata; ++ struct mlxreg_core_platform_data *led_pdata = priv->pdata; + struct mlxreg_core_data *data = led_pdata->data; + struct mlxreg_led_data *led_data; + struct led_classdev *led_cdev; +@@ -266,7 +266,7 @@ static int mlxreg_led_config(struct mlxreg_led_priv_data *priv) + + static int mlxreg_led_probe(struct platform_device *pdev) + { +- struct mlxreg_core_led_platform_data *led_pdata; ++ struct mlxreg_core_platform_data *led_pdata; + struct mlxreg_led_priv_data *priv; + + led_pdata = dev_get_platdata(&pdev->dev); +diff --git a/drivers/platform/mellanox/Kconfig b/drivers/platform/mellanox/Kconfig +index b197cc1..5c6dc29 100644 +--- a/drivers/platform/mellanox/Kconfig ++++ b/drivers/platform/mellanox/Kconfig +@@ -22,4 +22,15 @@ config MLXREG_HOTPLUG + This driver handles hot-plug events for the power suppliers, power + cables and fans on the wide range Mellanox IB and Ethernet systems. + ++config MLXREG_IO ++ tristate "Mellanox platform register driver support" ++ depends on REGMAP ++ depends on HWMON ++ ---help--- ++ This driver allows access to Mellanox programmable device register ++ space trough sysfs interface. The set of registers for sysfs access ++ are defined per system type bases and includes the registers related ++ to system resets operation, system reset causes monitoring and some ++ kinds of mux selection. ++ + endif # MELLANOX_PLATFORM +diff --git a/drivers/platform/mellanox/Makefile b/drivers/platform/mellanox/Makefile +index f58d089..b9a2692 100644 +--- a/drivers/platform/mellanox/Makefile ++++ b/drivers/platform/mellanox/Makefile +@@ -1 +1,2 @@ + obj-$(CONFIG_MLXREG_HOTPLUG) += mlxreg-hotplug.o ++obj-$(CONFIG_MLXREG_IO) += mlxreg-io.o +diff --git a/drivers/platform/mellanox/mlxreg-io.c b/drivers/platform/mellanox/mlxreg-io.c +new file mode 100644 +index 0000000..f7434ca +--- /dev/null ++++ b/drivers/platform/mellanox/mlxreg-io.c +@@ -0,0 +1,211 @@ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++/* Attribute parameters. */ ++#define MLXREG_IO_ATT_SIZE 10 ++#define MLXREG_IO_ATT_NUM 48 ++ ++/** ++ * struct mlxreg_io_priv_data - driver's private data: ++ * ++ * @pdev: platform device; ++ * @pdata: platform data; ++ * @hwmon: hwmon device; ++ * @mlxreg_io_attr: sysfs attributes array; ++ * @mlxreg_io_dev_attr: sysfs sensor device attribute array; ++ * @group: sysfs attribute group; ++ * @groups: list of sysfs attribute group for hwmon registration; ++ */ ++struct mlxreg_io_priv_data { ++ struct platform_device *pdev; ++ struct mlxreg_core_platform_data *pdata; ++ struct device *hwmon; ++ struct attribute *mlxreg_io_attr[MLXREG_IO_ATT_NUM + 1]; ++ struct sensor_device_attribute mlxreg_io_dev_attr[MLXREG_IO_ATT_NUM]; ++ struct attribute_group group; ++ const struct attribute_group *groups[2]; ++}; ++ ++static ssize_t ++mlxreg_io_attr_show(struct device *dev, struct device_attribute *attr, ++ char *buf) ++{ ++ struct mlxreg_io_priv_data *priv = dev_get_drvdata(dev); ++ int index = to_sensor_dev_attr(attr)->index; ++ struct mlxreg_core_data *data = priv->pdata->data + index; ++ u32 regval = 0; ++ int ret; ++ ++ ret = regmap_read(priv->pdata->regmap, data->reg, ®val); ++ if (ret) ++ goto access_error; ++ ++ if (!data->bit) ++ regval = !!(regval & ~data->mask); ++ ++ return sprintf(buf, "%u\n", regval); ++ ++access_error: ++ return ret; ++} ++ ++static ssize_t ++mlxreg_io_attr_store(struct device *dev, struct device_attribute *attr, ++ const char *buf, size_t len) ++{ ++ struct mlxreg_io_priv_data *priv = dev_get_drvdata(dev); ++ int index = to_sensor_dev_attr(attr)->index; ++ struct mlxreg_core_data *data = priv->pdata->data + index; ++ u32 val, regval; ++ int ret; ++ ++ ret = kstrtou32(buf, MLXREG_IO_ATT_SIZE, &val); ++ if (ret) ++ return ret; ++ ++ ret = regmap_read(priv->pdata->regmap, data->reg, ®val); ++ if (ret) ++ goto access_error; ++ ++ regval &= data->mask; ++ ++ val = !!val; ++ if (val) ++ regval |= ~data->mask; ++ else ++ regval &= data->mask; ++ ++ ret = regmap_write(priv->pdata->regmap, data->reg, regval); ++ if (ret) ++ goto access_error; ++ ++ return len; ++ ++access_error: ++ dev_err(&priv->pdev->dev, "Bus access error\n"); ++ return ret; ++} ++ ++static int mlxreg_io_attr_init(struct mlxreg_io_priv_data *priv) ++{ ++ int i; ++ ++ priv->group.attrs = devm_kzalloc(&priv->pdev->dev, ++ priv->pdata->counter * ++ sizeof(struct attribute *), ++ GFP_KERNEL); ++ if (!priv->group.attrs) ++ return -ENOMEM; ++ ++ for (i = 0; i < priv->pdata->counter; i++) { ++ priv->mlxreg_io_attr[i] = ++ &priv->mlxreg_io_dev_attr[i].dev_attr.attr; ++ ++ /* Set attribute name as a label. */ ++ priv->mlxreg_io_attr[i]->name = ++ devm_kasprintf(&priv->pdev->dev, GFP_KERNEL, ++ priv->pdata->data[i].label); ++ ++ if (!priv->mlxreg_io_attr[i]->name) { ++ dev_err(&priv->pdev->dev, "Memory allocation failed for sysfs attribute %d.\n", ++ i + 1); ++ return -ENOMEM; ++ } ++ ++ priv->mlxreg_io_dev_attr[i].dev_attr.attr.mode = ++ priv->pdata->data[i].mode; ++ switch (priv->pdata->data[i].mode) { ++ case 0200: ++ priv->mlxreg_io_dev_attr[i].dev_attr.store = ++ mlxreg_io_attr_store; ++ break; ++ ++ case 0444: ++ priv->mlxreg_io_dev_attr[i].dev_attr.show = ++ mlxreg_io_attr_show; ++ break; ++ ++ case 0644: ++ priv->mlxreg_io_dev_attr[i].dev_attr.show = ++ mlxreg_io_attr_show; ++ priv->mlxreg_io_dev_attr[i].dev_attr.store = ++ mlxreg_io_attr_store; ++ break; ++ ++ default: ++ dev_err(&priv->pdev->dev, "Bad access mode %u for attribute %s.\n", ++ priv->pdata->data[i].mode, ++ priv->mlxreg_io_attr[i]->name); ++ return -EINVAL; ++ } ++ ++ priv->mlxreg_io_dev_attr[i].dev_attr.attr.name = ++ priv->mlxreg_io_attr[i]->name; ++ priv->mlxreg_io_dev_attr[i].index = i; ++ sysfs_attr_init(&priv->mlxreg_io_dev_attr[i].dev_attr.attr); ++ } ++ ++ priv->group.attrs = priv->mlxreg_io_attr; ++ priv->groups[0] = &priv->group; ++ priv->groups[1] = NULL; ++ ++ return 0; ++} ++ ++static int mlxreg_io_probe(struct platform_device *pdev) ++{ ++ struct mlxreg_io_priv_data *priv; ++ int err; ++ ++ priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); ++ if (!priv) ++ return -ENOMEM; ++ ++ priv->pdata = dev_get_platdata(&pdev->dev); ++ if (!priv->pdata) { ++ dev_err(&pdev->dev, "Failed to get platform data.\n"); ++ return -EINVAL; ++ } ++ ++ priv->pdev = pdev; ++ ++ err = mlxreg_io_attr_init(priv); ++ if (err) { ++ dev_err(&priv->pdev->dev, "Failed to allocate attributes: %d\n", ++ err); ++ return err; ++ } ++ ++ priv->hwmon = devm_hwmon_device_register_with_groups(&pdev->dev, ++ "mlxreg_io", priv, priv->groups); ++ if (IS_ERR(priv->hwmon)) { ++ dev_err(&pdev->dev, "Failed to register hwmon device %ld\n", ++ PTR_ERR(priv->hwmon)); ++ return PTR_ERR(priv->hwmon); ++ } ++ ++ dev_set_drvdata(&pdev->dev, priv); ++ ++ return 0; ++} ++ ++static struct platform_driver mlxreg_io_driver = { ++ .driver = { ++ .name = "mlxreg-io", ++ }, ++ .probe = mlxreg_io_probe, ++}; ++ ++module_platform_driver(mlxreg_io_driver); ++ ++MODULE_AUTHOR("Vadim Pasternak "); ++MODULE_DESCRIPTION("Mellanox regmap I/O access driver"); ++MODULE_LICENSE("Dual BSD/GPL"); ++MODULE_ALIAS("platform:mlxreg-io"); +diff --git a/drivers/platform/x86/mlx-platform.c b/drivers/platform/x86/mlx-platform.c +index 49721c2..61cbe35 100644 +--- a/drivers/platform/x86/mlx-platform.c ++++ b/drivers/platform/x86/mlx-platform.c +@@ -47,16 +47,31 @@ + /* LPC bus IO offsets */ + #define MLXPLAT_CPLD_LPC_I2C_BASE_ADRR 0x2000 + #define MLXPLAT_CPLD_LPC_REG_BASE_ADRR 0x2500 ++#define MLXPLAT_CPLD_LPC_REG_CPLD1_VER_OFF 0x00 ++#define MLXPLAT_CPLD_LPC_REG_CPLD2_VER_OFF 0x01 ++#define MLXPLAT_CPLD_LPC_REG_RESET_CAUSE_OFF 0x1d + #define MLXPLAT_CPLD_LPC_REG_LED1_OFF 0x20 + #define MLXPLAT_CPLD_LPC_REG_LED2_OFF 0x21 + #define MLXPLAT_CPLD_LPC_REG_LED3_OFF 0x22 + #define MLXPLAT_CPLD_LPC_REG_LED4_OFF 0x23 + #define MLXPLAT_CPLD_LPC_REG_LED5_OFF 0x24 ++#define MLXPLAT_CPLD_LPC_REG_GP1_OFF 0x30 ++#define MLXPLAT_CPLD_LPC_REG_WP1_OFF 0x31 ++#define MLXPLAT_CPLD_LPC_REG_GP2_OFF 0x32 ++#define MLXPLAT_CPLD_LPC_REG_WP2_OFF 0x33 + #define MLXPLAT_CPLD_LPC_REG_AGGR_OFF 0x3a ++#define MLXPLAT_CPLD_LPC_REG_AGGR_MASK_OFF 0x3b + #define MLXPLAT_CPLD_LPC_REG_AGGR_LOW_OFF 0x40 ++#define MLXPLAT_CPLD_LPC_REG_AGGR_LOW_MASK_OFF 0x41 + #define MLXPLAT_CPLD_LPC_REG_PSU_OFF 0x58 ++#define MLXPLAT_CPLD_LPC_REG_PSU_EVENT_OFF 0x59 ++#define MLXPLAT_CPLD_LPC_REG_PSU_MASK_OFF 0x5a + #define MLXPLAT_CPLD_LPC_REG_PWR_OFF 0x64 ++#define MLXPLAT_CPLD_LPC_REG_PWR_EVENT_OFF 0x65 ++#define MLXPLAT_CPLD_LPC_REG_PWR_MASK_OFF 0x66 + #define MLXPLAT_CPLD_LPC_REG_FAN_OFF 0x88 ++#define MLXPLAT_CPLD_LPC_REG_FAN_EVENT_OFF 0x89 ++#define MLXPLAT_CPLD_LPC_REG_FAN_MASK_OFF 0x8a + #define MLXPLAT_CPLD_LPC_IO_RANGE 0x100 + #define MLXPLAT_CPLD_LPC_I2C_CH1_OFF 0xdb + #define MLXPLAT_CPLD_LPC_I2C_CH2_OFF 0xda +@@ -100,12 +115,14 @@ + * @pdev_mux - array of mux platform devices + * @pdev_hotplug - hotplug platform devices + * @pdev_led - led platform devices ++ * @pdev_io_regs - register access platform devices + */ + struct mlxplat_priv { + struct platform_device *pdev_i2c; + struct platform_device *pdev_mux[MLXPLAT_CPLD_LPC_MUX_DEVS]; + struct platform_device *pdev_hotplug; + struct platform_device *pdev_led; ++ struct platform_device *pdev_io_regs; + }; + + /* Regions for LPC I2C controller and LPC base register space */ +@@ -643,7 +660,7 @@ static struct mlxreg_core_data mlxplat_mlxcpld_default_led_data[] = { + }, + }; + +-static struct mlxreg_core_led_platform_data mlxplat_default_led_data = { ++static struct mlxreg_core_platform_data mlxplat_default_led_data = { + .data = mlxplat_mlxcpld_default_led_data, + .counter = ARRAY_SIZE(mlxplat_mlxcpld_default_led_data), + }; +@@ -697,7 +714,7 @@ static struct mlxreg_core_data mlxplat_mlxcpld_msn21xx_led_data[] = { + }, + }; + +-static struct mlxreg_core_led_platform_data mlxplat_msn21xx_led_data = { ++static struct mlxreg_core_platform_data mlxplat_msn21xx_led_data = { + .data = mlxplat_mlxcpld_msn21xx_led_data, + .counter = ARRAY_SIZE(mlxplat_mlxcpld_msn21xx_led_data), + }; +@@ -786,11 +803,105 @@ static struct mlxreg_core_data mlxplat_mlxcpld_default_ng_led_data[] = { + }, + }; + +-static struct mlxreg_core_led_platform_data mlxplat_default_ng_led_data = { ++static struct mlxreg_core_platform_data mlxplat_default_ng_led_data = { + .data = mlxplat_mlxcpld_default_ng_led_data, + .counter = ARRAY_SIZE(mlxplat_mlxcpld_default_ng_led_data), + }; + ++static bool mlxplat_mlxcpld_writeable_reg(struct device *dev, unsigned int reg) ++{ ++ switch (reg) { ++ case MLXPLAT_CPLD_LPC_REG_LED1_OFF: ++ case MLXPLAT_CPLD_LPC_REG_LED2_OFF: ++ case MLXPLAT_CPLD_LPC_REG_LED3_OFF: ++ case MLXPLAT_CPLD_LPC_REG_LED4_OFF: ++ case MLXPLAT_CPLD_LPC_REG_LED5_OFF: ++ case MLXPLAT_CPLD_LPC_REG_GP1_OFF: ++ case MLXPLAT_CPLD_LPC_REG_WP1_OFF: ++ case MLXPLAT_CPLD_LPC_REG_GP2_OFF: ++ case MLXPLAT_CPLD_LPC_REG_WP2_OFF: ++ case MLXPLAT_CPLD_LPC_REG_AGGR_MASK_OFF: ++ case MLXPLAT_CPLD_LPC_REG_AGGR_LOW_MASK_OFF: ++ case MLXPLAT_CPLD_LPC_REG_PSU_EVENT_OFF: ++ case MLXPLAT_CPLD_LPC_REG_PSU_MASK_OFF: ++ case MLXPLAT_CPLD_LPC_REG_PWR_EVENT_OFF: ++ case MLXPLAT_CPLD_LPC_REG_PWR_MASK_OFF: ++ case MLXPLAT_CPLD_LPC_REG_FAN_EVENT_OFF: ++ case MLXPLAT_CPLD_LPC_REG_FAN_MASK_OFF: ++ return true; ++ } ++ return false; ++} ++ ++static bool mlxplat_mlxcpld_readable_reg(struct device *dev, unsigned int reg) ++{ ++ switch (reg) { ++ case MLXPLAT_CPLD_LPC_REG_CPLD1_VER_OFF: ++ case MLXPLAT_CPLD_LPC_REG_CPLD2_VER_OFF: ++ case MLXPLAT_CPLD_LPC_REG_RESET_CAUSE_OFF: ++ case MLXPLAT_CPLD_LPC_REG_LED1_OFF: ++ case MLXPLAT_CPLD_LPC_REG_LED2_OFF: ++ case MLXPLAT_CPLD_LPC_REG_LED3_OFF: ++ case MLXPLAT_CPLD_LPC_REG_LED4_OFF: ++ case MLXPLAT_CPLD_LPC_REG_LED5_OFF: ++ case MLXPLAT_CPLD_LPC_REG_GP1_OFF: ++ case MLXPLAT_CPLD_LPC_REG_WP1_OFF: ++ case MLXPLAT_CPLD_LPC_REG_GP2_OFF: ++ case MLXPLAT_CPLD_LPC_REG_WP2_OFF: ++ case MLXPLAT_CPLD_LPC_REG_AGGR_OFF: ++ case MLXPLAT_CPLD_LPC_REG_AGGR_MASK_OFF: ++ case MLXPLAT_CPLD_LPC_REG_AGGR_LOW_OFF: ++ case MLXPLAT_CPLD_LPC_REG_AGGR_LOW_MASK_OFF: ++ case MLXPLAT_CPLD_LPC_REG_PSU_OFF: ++ case MLXPLAT_CPLD_LPC_REG_PSU_EVENT_OFF: ++ case MLXPLAT_CPLD_LPC_REG_PSU_MASK_OFF: ++ case MLXPLAT_CPLD_LPC_REG_PWR_OFF: ++ case MLXPLAT_CPLD_LPC_REG_PWR_EVENT_OFF: ++ case MLXPLAT_CPLD_LPC_REG_PWR_MASK_OFF: ++ case MLXPLAT_CPLD_LPC_REG_FAN_OFF: ++ case MLXPLAT_CPLD_LPC_REG_FAN_EVENT_OFF: ++ case MLXPLAT_CPLD_LPC_REG_FAN_MASK_OFF: ++ return true; ++ } ++ return false; ++} ++ ++static bool mlxplat_mlxcpld_volatile_reg(struct device *dev, unsigned int reg) ++{ ++ switch (reg) { ++ case MLXPLAT_CPLD_LPC_REG_CPLD1_VER_OFF: ++ case MLXPLAT_CPLD_LPC_REG_CPLD2_VER_OFF: ++ case MLXPLAT_CPLD_LPC_REG_RESET_CAUSE_OFF: ++ case MLXPLAT_CPLD_LPC_REG_LED1_OFF: ++ case MLXPLAT_CPLD_LPC_REG_LED2_OFF: ++ case MLXPLAT_CPLD_LPC_REG_LED3_OFF: ++ case MLXPLAT_CPLD_LPC_REG_LED4_OFF: ++ case MLXPLAT_CPLD_LPC_REG_LED5_OFF: ++ case MLXPLAT_CPLD_LPC_REG_GP1_OFF: ++ case MLXPLAT_CPLD_LPC_REG_GP2_OFF: ++ case MLXPLAT_CPLD_LPC_REG_AGGR_OFF: ++ case MLXPLAT_CPLD_LPC_REG_AGGR_MASK_OFF: ++ case MLXPLAT_CPLD_LPC_REG_AGGR_LOW_OFF: ++ case MLXPLAT_CPLD_LPC_REG_AGGR_LOW_MASK_OFF: ++ case MLXPLAT_CPLD_LPC_REG_PSU_OFF: ++ case MLXPLAT_CPLD_LPC_REG_PSU_EVENT_OFF: ++ case MLXPLAT_CPLD_LPC_REG_PSU_MASK_OFF: ++ case MLXPLAT_CPLD_LPC_REG_PWR_OFF: ++ case MLXPLAT_CPLD_LPC_REG_PWR_EVENT_OFF: ++ case MLXPLAT_CPLD_LPC_REG_PWR_MASK_OFF: ++ case MLXPLAT_CPLD_LPC_REG_FAN_OFF: ++ case MLXPLAT_CPLD_LPC_REG_FAN_EVENT_OFF: ++ case MLXPLAT_CPLD_LPC_REG_FAN_MASK_OFF: ++ return true; ++ } ++ return false; ++} ++ ++static const struct reg_default mlxplat_mlxcpld_regmap_default[] = { ++ { MLXPLAT_CPLD_LPC_REG_WP1_OFF, 0x00 }, ++ { MLXPLAT_CPLD_LPC_REG_WP2_OFF, 0x00 }, ++}; ++ + static int + mlxplat_mlxcpld_reg_read(void *context, unsigned int reg, unsigned int *val) + { +@@ -809,6 +920,12 @@ const struct regmap_config mlxplat_mlxcpld_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = 255, ++ .cache_type = REGCACHE_FLAT, ++ .writeable_reg = mlxplat_mlxcpld_writeable_reg, ++ .readable_reg = mlxplat_mlxcpld_readable_reg, ++ .volatile_reg = mlxplat_mlxcpld_volatile_reg, ++ .reg_defaults = mlxplat_mlxcpld_regmap_default, ++ .num_reg_defaults = ARRAY_SIZE(mlxplat_mlxcpld_regmap_default), + .reg_read = mlxplat_mlxcpld_reg_read, + .reg_write = mlxplat_mlxcpld_reg_write, + }; +@@ -817,9 +934,38 @@ static struct resource mlxplat_mlxcpld_resources[] = { + [0] = DEFINE_RES_IRQ_NAMED(17, "mlxreg-hotplug"), + }; + +-struct platform_device *mlxplat_dev; +-struct mlxreg_core_hotplug_platform_data *mlxplat_hotplug; +-struct mlxreg_core_led_platform_data *mlxplat_led; ++static struct mlxreg_core_data mlxplat_mlxcpld_default_regs_io_data[] = { ++ { "cpld1_version", MLXPLAT_CPLD_LPC_REG_CPLD1_VER_OFF, 0x00, ++ GENMASK(7, 0), 0444 }, ++ { "cpld2_version", MLXPLAT_CPLD_LPC_REG_CPLD2_VER_OFF, 0x00, ++ GENMASK(7, 0), 0444 }, ++ { "cause_long_pb", MLXPLAT_CPLD_LPC_REG_RESET_CAUSE_OFF, ++ GENMASK(7, 0) & ~BIT(0), 0x00, 0444 }, ++ { "cause_short_pb", MLXPLAT_CPLD_LPC_REG_RESET_CAUSE_OFF, ++ GENMASK(7, 0) & ~BIT(1), 0x00, 0444 }, ++ { "cause_pwr_aux", MLXPLAT_CPLD_LPC_REG_RESET_CAUSE_OFF, ++ GENMASK(7, 0) & ~BIT(2), 0x00, 0444 }, ++ { "cause_pwr_fail", MLXPLAT_CPLD_LPC_REG_RESET_CAUSE_OFF, ++ GENMASK(7, 0) & ~BIT(3), 0x00, 0444 }, ++ { "psu1_on", MLXPLAT_CPLD_LPC_REG_GP1_OFF, GENMASK(7, 0) & ~BIT(0), ++ 0x00, 0200 }, ++ { "psu2_on", MLXPLAT_CPLD_LPC_REG_GP1_OFF, GENMASK(7, 0) & ~BIT(1), ++ 0x00, 0200 }, ++ { "pwr_cycle", MLXPLAT_CPLD_LPC_REG_GP1_OFF, GENMASK(7, 0) & ~BIT(2), ++ 0x00, 0200 }, ++ { "select_iio", MLXPLAT_CPLD_LPC_REG_GP2_OFF, GENMASK(7, 0) & ~BIT(6), ++ 0x00, 0644 }, ++}; ++ ++static struct mlxreg_core_platform_data mlxplat_default_regs_io_data = { ++ .data = mlxplat_mlxcpld_default_regs_io_data, ++ .counter = ARRAY_SIZE(mlxplat_mlxcpld_default_regs_io_data), ++}; ++ ++static struct platform_device *mlxplat_dev; ++static struct mlxreg_core_hotplug_platform_data *mlxplat_hotplug; ++static struct mlxreg_core_platform_data *mlxplat_led; ++static struct mlxreg_core_platform_data *mlxplat_regs_io; + + static int __init mlxplat_dmi_default_matched(const struct dmi_system_id *dmi) + { +@@ -832,6 +978,7 @@ static int __init mlxplat_dmi_default_matched(const struct dmi_system_id *dmi) + } + mlxplat_hotplug = &mlxplat_mlxcpld_default_data; + mlxplat_led = &mlxplat_default_led_data; ++ mlxplat_regs_io = &mlxplat_default_regs_io_data; + + return 1; + }; +@@ -847,6 +994,7 @@ static int __init mlxplat_dmi_msn21xx_matched(const struct dmi_system_id *dmi) + } + mlxplat_hotplug = &mlxplat_mlxcpld_msn21xx_data; + mlxplat_led = &mlxplat_msn21xx_led_data; ++ mlxplat_regs_io = &mlxplat_default_regs_io_data; + + return 1; + }; +@@ -862,6 +1010,7 @@ static int __init mlxplat_dmi_msn274x_matched(const struct dmi_system_id *dmi) + } + mlxplat_hotplug = &mlxplat_mlxcpld_msn274x_data; + mlxplat_led = &mlxplat_default_led_data; ++ mlxplat_regs_io = &mlxplat_default_regs_io_data; + + return 1; + }; +@@ -877,6 +1026,7 @@ static int __init mlxplat_dmi_qmb7xx_matched(const struct dmi_system_id *dmi) + } + mlxplat_hotplug = &mlxplat_mlxcpld_default_ng_data; + mlxplat_led = &mlxplat_default_ng_led_data; ++ mlxplat_regs_io = &mlxplat_default_regs_io_data; + + return 1; + }; +@@ -892,6 +1042,7 @@ static int __init mlxplat_dmi_msn201x_matched(const struct dmi_system_id *dmi) + } + mlxplat_hotplug = &mlxplat_mlxcpld_msn201x_data; + mlxplat_led = &mlxplat_msn21xx_led_data; ++ mlxplat_regs_io = &mlxplat_default_regs_io_data; + + return 1; + }; +@@ -974,7 +1125,7 @@ static int __init mlxplat_init(void) + { + struct mlxplat_priv *priv; + void __iomem *base; +- int i, err = 0; ++ int i, j, err = 0; + + if (!dmi_check_system(mlxplat_dmi_table)) + return -ENODEV; +@@ -1023,6 +1174,15 @@ static int __init mlxplat_init(void) + if (IS_ERR(mlxplat_hotplug->regmap)) + goto fail_platform_mux_register; + ++ /* Set default registers. */ ++ for (j = 0; j < mlxplat_mlxcpld_regmap_config.num_reg_defaults; j++) { ++ err = regmap_write(mlxplat_hotplug->regmap, ++ mlxplat_mlxcpld_regmap_default[j].reg, ++ mlxplat_mlxcpld_regmap_default[j].def); ++ if (err) ++ goto fail_platform_mux_register; ++ } ++ + priv->pdev_hotplug = platform_device_register_resndata( + &mlxplat_dev->dev, "mlxreg-hotplug", + PLATFORM_DEVID_NONE, +@@ -1044,8 +1204,26 @@ static int __init mlxplat_init(void) + goto fail_platform_hotplug_register; + } + ++ mlxplat_regs_io->regmap = mlxplat_hotplug->regmap; ++ priv->pdev_io_regs = platform_device_register_resndata( ++ &mlxplat_dev->dev, "mlxreg-io", ++ PLATFORM_DEVID_NONE, NULL, 0, ++ mlxplat_regs_io, sizeof(*mlxplat_regs_io)); ++ if (IS_ERR(priv->pdev_io_regs)) { ++ err = PTR_ERR(priv->pdev_io_regs); ++ goto fail_platform_led_register; ++ } ++ ++ /* Sync registers with hardware. */ ++ regcache_mark_dirty(mlxplat_hotplug->regmap); ++ err = regcache_sync(mlxplat_hotplug->regmap); ++ if (err) ++ goto fail_platform_led_register; ++ + return 0; + ++fail_platform_led_register: ++ platform_device_unregister(priv->pdev_led); + fail_platform_hotplug_register: + platform_device_unregister(priv->pdev_hotplug); + fail_platform_mux_register: +@@ -1064,6 +1242,7 @@ static void __exit mlxplat_exit(void) + struct mlxplat_priv *priv = platform_get_drvdata(mlxplat_dev); + int i; + ++ platform_device_unregister(priv->pdev_io_regs); + platform_device_unregister(priv->pdev_led); + platform_device_unregister(priv->pdev_hotplug); + +diff --git a/include/linux/platform_data/mlxreg.h b/include/linux/platform_data/mlxreg.h +index dd471c5..c25623b 100644 +--- a/include/linux/platform_data/mlxreg.h ++++ b/include/linux/platform_data/mlxreg.h +@@ -61,6 +61,7 @@ struct mlxreg_hotplug_device { + * @label: attribute register offset; + * @reg: attribute register; + * @mask: attribute access mask; ++ * @mode: access mode; + * @bit: attribute effective bit; + * @np - pointer to node platform associated with attribute; + * @hpdev - hotplug device data; +@@ -72,6 +73,7 @@ struct mlxreg_core_data { + u32 reg; + u32 mask; + u32 bit; ++ umode_t mode; + struct device_node *np; + struct mlxreg_hotplug_device hpdev; + u8 health_cntr; +@@ -104,13 +106,13 @@ struct mlxreg_core_item { + }; + + /** +- * struct mlxreg_core_led_platform_data - led platform data: ++ * struct mlxreg_core_platform_data - platform data: + * + * @led_data: led private data; + * @regmap: register map of parent device; + * @counter: number of led instances; + */ +-struct mlxreg_core_led_platform_data { ++struct mlxreg_core_platform_data { + struct mlxreg_core_data *data; + void *regmap; + int counter; +-- +2.1.4 + diff --git a/patch/0010-config-mellanox-configuration.patch b/patch/0010-config-mellanox-configuration.patch new file mode 100644 index 000000000..da5aac81f --- /dev/null +++ b/patch/0010-config-mellanox-configuration.patch @@ -0,0 +1,142 @@ +From c951dd6c826de2dbcb1d239743448bb56753ef96 Mon Sep 17 00:00:00 2001 +From: root +Date: Mon, 27 Nov 2017 15:53:20 +0200 +Subject: [v4.9 backport 10/10] config: mellanox configuration + +It adds the next configuration flags, used by Mellanox systems: + +CONFIG_MLXSW_CORE=m +CONFIG_MLXSW_CORE_HWMON=y +CONFIG_MLXSW_CORE_THERMAL=y +CONFIG_MLXSW_CORE_QSFP=y +CONFIG_MLXSW_PCI=m +CONFIG_MLXSW_I2C=m +CONFIG_MLXSW_MINIMAL=m +CONFIG_I2C_MUX_REG=y +CONFIG_I2C_MUX_MLXCPLD=m +CONFIG_I2C_MLXCPLD=m +CONFIG_SENSORS_IIO_HWMON=y +CONFIG_PMBUS=y +CONFIG_SENSORS_PMBUS=y +CONFIG_SENSORS_LM25066=m +CONFIG_SENSORS_TPS53679=m +CONFIG_SENSORS_UCD9000=m +CONFIG_SENSORS_UCD9200=m +CONFIG_LEDS_MLXREG=m +CONFIG_MLX_PLATFORM=m +CONFIG_MELLANOX_PLATFORM=y +CONFIG_MLXREG_HOTPLUG=m +CONFIG_MLXREG_IO=m +CONFIG_MAX1363=y + +Signed-off-by: Vadim Pastrenak +--- + debian/build/build_amd64_none_amd64/.config | 28 +++++++++++++++++++++++----- + 1 file changed, 23 insertions(+), 5 deletions(-) + +diff --git a/debian/build/build_amd64_none_amd64/.config b/debian/build/build_amd64_none_amd64/.config +index 88a71ef..f1930a1 100644 +--- a/debian/build/build_amd64_none_amd64/.config ++++ b/debian/build/build_amd64_none_amd64/.config +@@ -2632,7 +2632,13 @@ CONFIG_MLX4_DEBUG=y + CONFIG_MLX5_CORE=m + CONFIG_MLX5_CORE_EN=y + CONFIG_MLX5_CORE_EN_DCB=y +-# CONFIG_MLXSW_CORE is not set ++CONFIG_MLXSW_CORE=m ++CONFIG_MLXSW_CORE_HWMON=y ++CONFIG_MLXSW_CORE_THERMAL=y ++CONFIG_MLXSW_CORE_QSFP=y ++CONFIG_MLXSW_PCI=m ++CONFIG_MLXSW_I2C=m ++CONFIG_MLXSW_MINIMAL=m + CONFIG_NET_VENDOR_MICREL=y + # CONFIG_KS8842 is not set + # CONFIG_KS8851 is not set +@@ -3565,7 +3571,8 @@ CONFIG_I2C_MUX=m + # CONFIG_I2C_MUX_PCA9541 is not set + # CONFIG_I2C_MUX_PCA954x is not set + # CONFIG_I2C_MUX_PINCTRL is not set +-# CONFIG_I2C_MUX_REG is not set ++CONFIG_I2C_MUX_REG=y ++CONFIG_I2C_MUX_MLXCPLD=m + CONFIG_I2C_HELPER_AUTO=y + CONFIG_I2C_SMBUS=m + CONFIG_I2C_ALGOBIT=m +@@ -3632,6 +3639,7 @@ CONFIG_I2C_VIPERBOARD=m + # + # Other I2C/SMBus bus drivers + # ++CONFIG_I2C_MLXCPLD=m + CONFIG_I2C_STUB=m + # CONFIG_I2C_SLAVE is not set + # CONFIG_I2C_DEBUG_CORE is not set +@@ -3879,7 +3887,7 @@ CONFIG_SENSORS_G760A=m + # CONFIG_SENSORS_HIH6130 is not set + CONFIG_SENSORS_IBMAEM=m + CONFIG_SENSORS_IBMPEX=m +-# CONFIG_SENSORS_IIO_HWMON is not set ++CONFIG_SENSORS_IIO_HWMON=y + CONFIG_SENSORS_I5500=m + CONFIG_SENSORS_CORETEMP=m + CONFIG_SENSORS_IT87=m +@@ -3932,7 +3949,21 @@ CONFIG_SENSORS_NCT6775=m + # CONFIG_SENSORS_NCT7802 is not set + # CONFIG_SENSORS_NCT7904 is not set + CONFIG_SENSORS_PCF8591=m +-# CONFIG_PMBUS is not set ++CONFIG_PMBUS=m ++CONFIG_SENSORS_PMBUS=m ++CONFIG_SENSORS_LM25066=m ++CONFIG_SENSORS_TPS53679=m ++CONFIG_SENSORS_UCD9000=m ++CONFIG_SENSORS_UCD9200=m ++# CONFIG_SENSORS_ADM1275 is not set ++# CONFIG_SENSORS_LTC2978 is not set ++# CONFIG_SENSORS_LTC3815 is not set ++# CONFIG_SENSORS_MAX16064 is not set ++# CONFIG_SENSORS_MAX20751 is not set ++# CONFIG_SENSORS_MAX34440 is not set ++# CONFIG_SENSORS_MAX8688 is not set ++# CONFIG_SENSORS_TPS40422 is not set ++# CONFIG_SENSORS_ZL6100 is not set + # CONFIG_SENSORS_SHT15 is not set + CONFIG_SENSORS_SHT21=m + # CONFIG_SENSORS_SHT3x is not set +@@ -5904,6 +5919,9 @@ CONFIG_LEDS_MENF21BMC=m + # + # CONFIG_LEDS_BLINKM is not set + # CONFIG_LEDS_MLXCPLD is not set ++CONFIG_LEDS_MLXREG=m ++# CONFIG_LEDS_USER is not set ++# CONFIG_LEDS_NIC78BX is not set + + # + # LED Triggers +@@ -6483,10 +6497,14 @@ CONFIG_PVPANIC=m + CONFIG_INTEL_PMC_IPC=m + CONFIG_SURFACE_PRO3_BUTTON=m + # CONFIG_INTEL_PUNIT_IPC is not set ++CONFIG_MLX_PLATFORM=m + CONFIG_CHROME_PLATFORMS=y + CONFIG_CHROMEOS_LAPTOP=m + CONFIG_CHROMEOS_PSTORE=m + CONFIG_CROS_KBD_LED_BACKLIGHT=m ++CONFIG_MELLANOX_PLATFORM=y ++CONFIG_MLXREG_HOTPLUG=m ++CONFIG_MLXREG_IO=m + CONFIG_CLKDEV_LOOKUP=y + CONFIG_HAVE_CLK_PREPARE=y + CONFIG_COMMON_CLK=y +@@ -6621,7 +6639,7 @@ CONFIG_MMA9553=m + # CONFIG_INA2XX_ADC is not set + # CONFIG_LTC2485 is not set + # CONFIG_MAX1027 is not set +-# CONFIG_MAX1363 is not set ++CONFIG_MAX1363=y + # CONFIG_MCP320X is not set + # CONFIG_MCP3422 is not set + # CONFIG_NAU7802 is not set +-- +2.11.0 + diff --git a/patch/series b/patch/series index d8e332989..91afaecf1 100644 --- a/patch/series +++ b/patch/series @@ -1,29 +1,41 @@ +# This series applies on GIT commit 18c5597832fcf6988111b05a9a1607ae148723c +0001-i2c-mlxcpld-add-master-driver-for-Mellanox-systems.patch +0002-i2c-mux-mlxcpld-add-driver-for-Mellanox-systems.patch +0003-platform-mellanox-Introduce-Mellanox-hardware-platfo.patch +0004-platform-x86-Introduce-support-for-Mellanox-hotplug-.patch +0005-leds-add-driver-for-support-Mellanox-regmap-LEDs-for.patch +0006-Mellanox-switch-drivers-changes.patch +0007-hwmon-pmbus-Add-support-for-Intel-VID-protocol-VR13.patch +0008-hwmon-pmbus-Add-support-for-Texas-Instruments-tps536.patch +0009-platform-mellonox-introduce-mlxreg-io-driver-and-add.patch +0010-config-mellanox-configuration.patch +# # This series applies on GIT commit 1451b36b2b0d62178e42f648d8a18131af18f7d8 -kernel-sched-core-fix-cgroup-fork-race.patch -config-dell-s6000.patch -config-mlnx-sn2700.patch -config-dell-z9100.patch -config-ingrasys-s9100.patch -driver-at24-fix-odd-length-two-byte-access.patch -driver-hwmon-max6620.patch -driver-hwmon-max6620-fix-rpm-calc.patch -driver-hwmon-max6620-update.patch -driver-hwmon-lm75b-update.patch -driver-hwmon-pmbus-dni_dps460.patch -driver-hwmon-pmbus-dni_dps460-update-pmbus-core.patch -driver-i2c-bus-intel-ismt-add-delay-param.patch -driver-support-sff-8436-eeprom.patch -driver-support-sff-8436-eeprom-update.patch -driver-hwmon-pmbus-add-dps460-support.patch -driver-hwmon-pmbus-ucd9200-mlnx.patch -driver-arista-piix4-mux-patch.patch -driver-arista-net-tg3-dma-mask-4g-sb800.patch -driver-arista-net-tg3-access-regs-indirectly.patch -driver-arista-net-tg3-disallow-broadcom-default-mac.patch -driver-pca954x-i2c-mux-force-deselect-on-exit-flag.patch -linux-3.19-mmc-sdhci-Add-a-quirk-for-AMD-SDHC-transfer-mode-reg.patch -linux-3.19-mmc-sdhci-pci-enable-the-clear-transfer-mode-registe.patch -linux-3.19-mmc-sdhci-pci-enable-sdhci-doesn-t-support-hs200-qui.patch -rtnetlink-catch-EOPNOTSUPP-errors.patch -bridge-per-port-multicast-broadcast-flood-flags.patch -driver-support-tun-config-carrier-enable.patch \ No newline at end of file +# Tkernel-sched-core-fix-cgroup-fork-race.patch +# Tconfig-dell-s6000.patch +# Tconfig-mlnx-sn2700.patch +# Tconfig-dell-z9100.patch +# Tconfig-ingrasys-s9100.patch +# Tdriver-at24-fix-odd-length-two-byte-access.patch +# Tdriver-hwmon-max6620.patch +# Tdriver-hwmon-max6620-fix-rpm-calc.patch +# Tdriver-hwmon-max6620-update.patch +# Tdriver-hwmon-lm75b-update.patch +# Tdriver-hwmon-pmbus-dni_dps460.patch +# Tdriver-hwmon-pmbus-dni_dps460-update-pmbus-core.patch +# Tdriver-i2c-bus-intel-ismt-add-delay-param.patch +# Tdriver-support-sff-8436-eeprom.patch +# Tdriver-support-sff-8436-eeprom-update.patch +# Tdriver-hwmon-pmbus-add-dps460-support.patch +# Tdriver-hwmon-pmbus-ucd9200-mlnx.patch +# Tdriver-arista-piix4-mux-patch.patch +# Tdriver-arista-net-tg3-dma-mask-4g-sb800.patch +# Tdriver-arista-net-tg3-access-regs-indirectly.patch +# Tdriver-arista-net-tg3-disallow-broadcom-default-mac.patch +# Tdriver-pca954x-i2c-mux-force-deselect-on-exit-flag.patch +# Tlinux-3.19-mmc-sdhci-Add-a-quirk-for-AMD-SDHC-transfer-mode-reg.patch +# Tlinux-3.19-mmc-sdhci-pci-enable-the-clear-transfer-mode-registe.patch +# Tlinux-3.19-mmc-sdhci-pci-enable-sdhci-doesn-t-support-hs200-qui.patch +# Trtnetlink-catch-EOPNOTSUPP-errors.patch +# Tbridge-per-port-multicast-broadcast-flood-flags.patch +# Tdriver-support-tun-config-carrier-enable.patch