@@ -10,3 +10,9 @@ config PCI_PWRCTL_PWRSEQ
tristate
select POWER_SEQUENCING
select PCI_PWRCTL
+
+config PCI_PWRCTRL_TC956X
+ tristate "PCI Power Control driver for TC956x PCIe switch"
+ select PCI_PWRCTL
+ help
+ Say Y here to enable the pwrctrl driver for TC956x PCIe switch.
@@ -4,3 +4,4 @@ obj-$(CONFIG_PCI_PWRCTL) += pci-pwrctrl-core.o
pci-pwrctrl-core-y := core.o
obj-$(CONFIG_PCI_PWRCTL_PWRSEQ) += pci-pwrctrl-pwrseq.o
+obj-$(CONFIG_PCI_PWRCTRL_TC956X) += pci-pwrctrl-tc956x.o
new file mode 100644
@@ -0,0 +1,625 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2024 Qualcomm Innovation Center, Inc. All rights reserved.
+ */
+
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/i2c.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <linux/pci.h>
+#include <linux/pci-pwrctrl.h>
+#include <linux/platform_device.h>
+#include <linux/regulator/consumer.h>
+#include <linux/string.h>
+#include <linux/types.h>
+#include <linux/unaligned.h>
+
+#include "../pci.h"
+
+#define TC956X_GPIO_CONFIG 0x801208
+#define TC956X_RESET_GPIO 0x801210
+
+#define TC956X_BUS_CONTROL 0x801014
+
+#define TC956X_PORT_L0S_DELAY 0x82496c
+#define TC956X_PORT_L1_DELAY 0x824970
+
+#define TC956X_EMBEDDED_ETH_DELAY 0x8200d8
+#define TC956X_ETH_L1_DELAY_MASK GENMASK(27, 18)
+#define TC956X_ETH_L1_DELAY_VALUE(x) FIELD_PREP(TC956X_ETH_L1_DELAY_MASK, x)
+#define TC956X_ETH_L0S_DELAY_MASK GENMASK(17, 13)
+#define TC956X_ETH_L0S_DELAY_VALUE(x) FIELD_PREP(TC956X_ETH_L0S_DELAY_MASK, x)
+
+#define TC956X_NFTS_2_5_GT 0x824978
+#define TC956X_NFTS_5_GT 0x82497c
+
+#define TC956X_PORT_LANE_ACCESS_ENABLE 0x828000
+
+#define TC956X_PHY_RATE_CHANGE_OVERRIDE 0x828040
+#define TC956X_PHY_RATE_CHANGE 0x828050
+
+#define TC956X_TX_MARGIN 0x828234
+
+#define TC956X_DFE_ENABLE 0x828a04
+#define TC956X_DFE_EQ0_MODE 0x828a08
+#define TC956X_DFE_EQ1_MODE 0x828a0c
+#define TC956X_DFE_EQ2_MODE 0x828a14
+#define TC956X_DFE_PD_MASK 0x828254
+
+#define TC956X_PORT_SELECT 0x82c02c
+#define TC956X_PORT_ACCESS_ENABLE 0x82c030
+
+#define TC956X_POWER_CONTROL 0x82b09c
+#define TC956X_POWER_CONTROL_OVREN 0x82b2c8
+
+#define TC956X_GPIO_MASK 0xfffffff3
+
+#define TC956X_TX_MARGIN_MIN_VAL 400000
+
+struct tc956x_pwrctrl_reg_setting {
+ unsigned int offset;
+ unsigned int val;
+};
+
+enum tc956x_pwrctrl_ports {
+ TC956X_USP,
+ TC956X_DSP1,
+ TC956X_DSP2,
+ TC956X_DSP3,
+ TC956X_ETHERNET,
+ TC956X_MAX
+};
+
+struct tc956x_pwrctrl_cfg {
+ u32 l0s_delay;
+ u32 l1_delay;
+ u32 tx_amp;
+ u8 nfts[2]; /* GEN1 & GEN2*/
+ bool disable_dfe;
+ bool disable_port;
+};
+
+#define TC956X_PWRCTL_MAX_SUPPLY 6
+
+struct tc956x_pwrctrl_ctx {
+ struct regulator_bulk_data supplies[TC956X_PWRCTL_MAX_SUPPLY];
+ struct tc956x_pwrctrl_cfg cfg[TC956X_MAX];
+ struct gpio_desc *reset_gpio;
+ struct i2c_adapter *adapter;
+ struct i2c_client *client;
+ struct pci_pwrctrl pwrctrl;
+};
+
+/*
+ * downstream port power off sequence, hardcoding the address
+ * as we don't know register names for these register offsets.
+ */
+static const struct tc956x_pwrctrl_reg_setting common_pwroff_seq[] = {
+ {0x82900c, 0x1},
+ {0x829010, 0x1},
+ {0x829018, 0x0},
+ {0x829020, 0x1},
+ {0x82902c, 0x1},
+ {0x829030, 0x1},
+ {0x82903c, 0x1},
+ {0x829058, 0x0},
+ {0x82905c, 0x1},
+ {0x829060, 0x1},
+ {0x8290cc, 0x1},
+ {0x8290d0, 0x1},
+ {0x8290d8, 0x1},
+ {0x8290e0, 0x1},
+ {0x8290e8, 0x1},
+ {0x8290ec, 0x1},
+ {0x8290f4, 0x1},
+ {0x82910c, 0x1},
+ {0x829110, 0x1},
+ {0x829114, 0x1},
+};
+
+static const struct tc956x_pwrctrl_reg_setting dsp1_pwroff_seq[] = {
+ {TC956X_PORT_ACCESS_ENABLE, 0x2},
+ {TC956X_PORT_LANE_ACCESS_ENABLE, 0x3},
+ {TC956X_POWER_CONTROL, 0x014f4804},
+ {TC956X_POWER_CONTROL_OVREN, 0x1},
+ {TC956X_PORT_ACCESS_ENABLE, 0x4},
+};
+
+static const struct tc956x_pwrctrl_reg_setting dsp2_pwroff_seq[] = {
+ {TC956X_PORT_ACCESS_ENABLE, 0x8},
+ {TC956X_PORT_LANE_ACCESS_ENABLE, 0x1},
+ {TC956X_POWER_CONTROL, 0x014f4804},
+ {TC956X_POWER_CONTROL_OVREN, 0x1},
+ {TC956X_PORT_ACCESS_ENABLE, 0x8},
+};
+
+/*
+ * Since all transfers are initiated by the probe, no locks are necessary,
+ * as there are no concurrent calls.
+ */
+static int tc956x_pwrctrl_i2c_write(struct i2c_client *client,
+ u32 reg_addr, u32 reg_val)
+{
+ struct i2c_msg msg;
+ u8 msg_buf[7];
+ int ret;
+
+ msg.addr = client->addr;
+ msg.len = 7;
+ msg.flags = 0;
+
+ /* Big Endian for reg addr */
+ put_unaligned_be24(reg_addr, &msg_buf[0]);
+
+ /* Little Endian for reg val */
+ put_unaligned_le32(reg_val, &msg_buf[3]);
+
+ msg.buf = msg_buf;
+ ret = i2c_transfer(client->adapter, &msg, 1);
+ return ret == 1 ? 0 : ret;
+}
+
+static int tc956x_pwrctrl_i2c_read(struct i2c_client *client,
+ u32 reg_addr, u32 *reg_val)
+{
+ struct i2c_msg msg[2];
+ u8 wr_data[3];
+ u32 rd_data;
+ int ret;
+
+ msg[0].addr = client->addr;
+ msg[0].len = 3;
+ msg[0].flags = 0;
+
+ /* Big Endian for reg addr */
+ put_unaligned_be24(reg_addr, &wr_data[0]);
+
+ msg[0].buf = wr_data;
+
+ msg[1].addr = client->addr;
+ msg[1].len = 4;
+ msg[1].flags = I2C_M_RD;
+
+ msg[1].buf = (u8 *)&rd_data;
+
+ ret = i2c_transfer(client->adapter, &msg[0], 2);
+ if (ret == 2) {
+ *reg_val = get_unaligned_le32(&rd_data);
+ return 0;
+ }
+
+ /* If only one message successfully completed, return -EIO */
+ return ret == 1 ? -EIO : ret;
+}
+
+static int tc956x_pwrctrl_i2c_bulk_write(struct i2c_client *client,
+ const struct tc956x_pwrctrl_reg_setting *seq, int len)
+{
+ int ret, i;
+
+ for (i = 0; i < len; i++) {
+ ret = tc956x_pwrctrl_i2c_write(client, seq[i].offset, seq[i].val);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+static int tc956x_pwrctrl_disable_port(struct tc956x_pwrctrl_ctx *ctx,
+ enum tc956x_pwrctrl_ports port)
+{
+ struct tc956x_pwrctrl_cfg *cfg = &ctx->cfg[port];
+ const struct tc956x_pwrctrl_reg_setting *seq;
+ int ret, len;
+
+ if (!cfg->disable_port)
+ return 0;
+
+ if (port == TC956X_DSP1) {
+ seq = dsp1_pwroff_seq;
+ len = ARRAY_SIZE(dsp1_pwroff_seq);
+ } else {
+ seq = dsp2_pwroff_seq;
+ len = ARRAY_SIZE(dsp2_pwroff_seq);
+ }
+
+ ret = tc956x_pwrctrl_i2c_bulk_write(ctx->client, seq, len);
+ if (ret)
+ return ret;
+
+ return tc956x_pwrctrl_i2c_bulk_write(ctx->client,
+ common_pwroff_seq, ARRAY_SIZE(common_pwroff_seq));
+}
+
+static int tc956x_pwrctrl_set_l0s_l1_entry_delay(struct tc956x_pwrctrl_ctx *ctx,
+ enum tc956x_pwrctrl_ports port, bool is_l1, u32 ns)
+{
+ u32 rd_val, units;
+ int ret;
+
+ if (!ns)
+ return 0;
+
+ /* convert to units of 256ns */
+ units = ns / 256;
+
+ if (port == TC956X_ETHERNET) {
+ ret = tc956x_pwrctrl_i2c_read(ctx->client, TC956X_EMBEDDED_ETH_DELAY, &rd_val);
+ if (ret)
+ return ret;
+
+ if (is_l1)
+ rd_val = u32_replace_bits(rd_val, units, TC956X_ETH_L1_DELAY_MASK);
+ else
+ rd_val = u32_replace_bits(rd_val, units, TC956X_ETH_L0S_DELAY_MASK);
+
+ return tc956x_pwrctrl_i2c_write(ctx->client, TC956X_EMBEDDED_ETH_DELAY, rd_val);
+ }
+
+ ret = tc956x_pwrctrl_i2c_write(ctx->client, TC956X_PORT_SELECT, BIT(port));
+ if (ret)
+ return ret;
+
+ return tc956x_pwrctrl_i2c_write(ctx->client,
+ is_l1 ? TC956X_PORT_L1_DELAY : TC956X_PORT_L0S_DELAY, units);
+}
+
+static int tc956x_pwrctrl_set_tx_amplitude(struct tc956x_pwrctrl_ctx *ctx,
+ enum tc956x_pwrctrl_ports port, u32 amp)
+{
+ int port_access;
+
+ if (amp < TC956X_TX_MARGIN_MIN_VAL)
+ return 0;
+
+ /* txmargin = (Amp(uV) - 400000) / 3125 */
+ amp = (amp - TC956X_TX_MARGIN_MIN_VAL) / 3125;
+
+ switch (port) {
+ case TC956X_USP:
+ port_access = 0x1;
+ break;
+ case TC956X_DSP1:
+ port_access = 0x2;
+ break;
+ case TC956X_DSP2:
+ port_access = 0x8;
+ break;
+ default:
+ return -EINVAL;
+ };
+
+ struct tc956x_pwrctrl_reg_setting tx_amp_seq[] = {
+ {TC956X_PORT_ACCESS_ENABLE, port_access},
+ {TC956X_PORT_LANE_ACCESS_ENABLE, 0x3},
+ {TC956X_TX_MARGIN, amp},
+ };
+
+ return tc956x_pwrctrl_i2c_bulk_write(ctx->client, tx_amp_seq, ARRAY_SIZE(tx_amp_seq));
+}
+
+static int tc956x_pwrctrl_disable_dfe(struct tc956x_pwrctrl_ctx *ctx,
+ enum tc956x_pwrctrl_ports port)
+{
+ struct tc956x_pwrctrl_cfg *cfg = &ctx->cfg[port];
+ int port_access, lane_access = 0x3;
+ u32 phy_rate = 0x21;
+
+ if (!cfg->disable_dfe)
+ return 0;
+
+ switch (port) {
+ case TC956X_USP:
+ phy_rate = 0x1;
+ port_access = 0x1;
+ break;
+ case TC956X_DSP1:
+ port_access = 0x2;
+ break;
+ case TC956X_DSP2:
+ port_access = 0x8;
+ lane_access = 0x1;
+ break;
+ default:
+ return -EINVAL;
+ };
+
+ struct tc956x_pwrctrl_reg_setting disable_dfe_seq[] = {
+ {TC956X_PORT_ACCESS_ENABLE, port_access},
+ {TC956X_PORT_LANE_ACCESS_ENABLE, lane_access},
+ {TC956X_DFE_ENABLE, 0x0},
+ {TC956X_DFE_EQ0_MODE, 0x411},
+ {TC956X_DFE_EQ1_MODE, 0x11},
+ {TC956X_DFE_EQ2_MODE, 0x11},
+ {TC956X_DFE_PD_MASK, 0x7},
+ {TC956X_PHY_RATE_CHANGE_OVERRIDE, 0x10},
+ {TC956X_PHY_RATE_CHANGE, phy_rate},
+ {TC956X_PHY_RATE_CHANGE, 0x0},
+ {TC956X_PHY_RATE_CHANGE_OVERRIDE, 0x0},
+ };
+
+ return tc956x_pwrctrl_i2c_bulk_write(ctx->client,
+ disable_dfe_seq, ARRAY_SIZE(disable_dfe_seq));
+}
+
+static int tc956x_pwrctrl_set_nfts(struct tc956x_pwrctrl_ctx *ctx,
+ enum tc956x_pwrctrl_ports port, u8 *nfts)
+{
+ struct tc956x_pwrctrl_reg_setting nfts_seq[] = {
+ {TC956X_NFTS_2_5_GT, nfts[0]},
+ {TC956X_NFTS_5_GT, nfts[1]},
+ };
+ int ret;
+
+ if (!nfts[0])
+ return 0;
+
+ ret = tc956x_pwrctrl_i2c_write(ctx->client, TC956X_PORT_SELECT, BIT(port));
+ if (ret)
+ return ret;
+
+ return tc956x_pwrctrl_i2c_bulk_write(ctx->client, nfts_seq, ARRAY_SIZE(nfts_seq));
+}
+
+static int tc956x_pwrctrl_assert_deassert_reset(struct tc956x_pwrctrl_ctx *ctx, bool deassert)
+{
+ int ret, val;
+
+ ret = tc956x_pwrctrl_i2c_write(ctx->client, TC956X_GPIO_CONFIG, TC956X_GPIO_MASK);
+ if (ret)
+ return ret;
+
+ val = deassert ? 0xc : 0;
+
+ return tc956x_pwrctrl_i2c_write(ctx->client, TC956X_RESET_GPIO, val);
+}
+
+static int tc956x_pwrctrl_parse_device_dt(struct tc956x_pwrctrl_ctx *ctx, struct device_node *node,
+ enum tc956x_pwrctrl_ports port)
+{
+ struct tc956x_pwrctrl_cfg *cfg;
+ int ret;
+
+ cfg = &ctx->cfg[port];
+
+ /* Disable port if the status of the port is disabled. */
+ if (!of_device_is_available(node)) {
+ cfg->disable_port = true;
+ return 0;
+ };
+
+ ret = of_property_read_u32(node, "aspm-l0s-entry-delay-ns", &cfg->l0s_delay);
+ if (ret && ret != -EINVAL)
+ return ret;
+
+ ret = of_property_read_u32(node, "aspm-l1-entry-delay-ns", &cfg->l1_delay);
+ if (ret && ret != -EINVAL)
+ return ret;
+
+ ret = of_property_read_u32(node, "qcom,tx-amplitude-microvolt", &cfg->tx_amp);
+ if (ret && ret != -EINVAL)
+ return ret;
+
+ ret = of_property_read_u8_array(node, "nfts", cfg->nfts, 2);
+ if (ret && ret != -EINVAL)
+ return ret;
+
+ cfg->disable_dfe = of_property_read_bool(node, "qcom,no-dfe-support");
+
+ return 0;
+}
+
+static void tc956x_pwrctrl_power_off(struct tc956x_pwrctrl_ctx *ctx)
+{
+ gpiod_set_value(ctx->reset_gpio, 1);
+
+ regulator_bulk_disable(ARRAY_SIZE(ctx->supplies), ctx->supplies);
+}
+
+static int tc956x_pwrctrl_bring_up(struct tc956x_pwrctrl_ctx *ctx)
+{
+ struct tc956x_pwrctrl_cfg *cfg;
+ int ret, i;
+
+ ret = regulator_bulk_enable(ARRAY_SIZE(ctx->supplies), ctx->supplies);
+ if (ret < 0)
+ return dev_err_probe(ctx->pwrctrl.dev, ret, "cannot enable regulators\n");
+
+ gpiod_set_value(ctx->reset_gpio, 0);
+
+ /* wait for the internal osc frequency to stablise */
+ usleep_range(10000, 10500);
+
+ ret = tc956x_pwrctrl_assert_deassert_reset(ctx, false);
+ if (ret)
+ goto power_off;
+
+ for (i = 0; i < TC956X_MAX; i++) {
+ cfg = &ctx->cfg[i];
+ ret = tc956x_pwrctrl_disable_port(ctx, i);
+ if (ret) {
+ dev_err(ctx->pwrctrl.dev, "Disabling port failed\n");
+ goto power_off;
+ }
+
+ ret = tc956x_pwrctrl_set_l0s_l1_entry_delay(ctx, i, false, cfg->l0s_delay);
+ if (ret) {
+ dev_err(ctx->pwrctrl.dev, "Setting L0s entry delay failed\n");
+ goto power_off;
+ }
+
+ ret = tc956x_pwrctrl_set_l0s_l1_entry_delay(ctx, i, true, cfg->l1_delay);
+ if (ret) {
+ dev_err(ctx->pwrctrl.dev, "Setting L1 entry delay failed\n");
+ goto power_off;
+ }
+
+ ret = tc956x_pwrctrl_set_tx_amplitude(ctx, i, cfg->tx_amp);
+ if (ret) {
+ dev_err(ctx->pwrctrl.dev, "Setting Tx amplitube failed\n");
+ goto power_off;
+ }
+
+ ret = tc956x_pwrctrl_set_nfts(ctx, i, cfg->nfts);
+ if (ret) {
+ dev_err(ctx->pwrctrl.dev, "Setting nfts failed\n");
+ goto power_off;
+ }
+
+ ret = tc956x_pwrctrl_disable_dfe(ctx, i);
+ if (ret) {
+ dev_err(ctx->pwrctrl.dev, "Disabling DFE failed\n");
+ goto power_off;
+ }
+ }
+
+ ret = tc956x_pwrctrl_assert_deassert_reset(ctx, true);
+ if (!ret)
+ return 0;
+
+power_off:
+ tc956x_pwrctrl_power_off(ctx);
+ return ret;
+}
+
+static int tc956x_pwrctrl_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct pci_dev *pci_dev = to_pci_dev(dev->parent);
+ struct pci_host_bridge *bridge = pci_find_host_bridge(pci_dev->bus);
+ enum tc956x_pwrctrl_ports port;
+ struct tc956x_pwrctrl_ctx *ctx;
+ int ret, addr;
+
+ ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL);
+ if (!ctx)
+ return -ENOMEM;
+
+ ret = of_property_read_u32_index(pdev->dev.of_node, "i2c-parent", 1, &addr);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to read i2c-parent property\n");
+
+ ctx->adapter = of_find_i2c_adapter_by_node(of_parse_phandle(dev->of_node, "i2c-parent", 0));
+ of_node_put(dev->of_node);
+ if (!ctx->adapter)
+ return dev_err_probe(dev, -EPROBE_DEFER, "Failed to find I2C adapter\n");
+
+ ctx->client = i2c_new_dummy_device(ctx->adapter, addr);
+ if (IS_ERR(ctx->client)) {
+ dev_err(dev, "Failed to create I2C client\n");
+ i2c_put_adapter(ctx->adapter);
+ return PTR_ERR(ctx->client);
+ }
+
+ ctx->supplies[0].supply = "vddc";
+ ctx->supplies[1].supply = "vdd18";
+ ctx->supplies[2].supply = "vdd09";
+ ctx->supplies[3].supply = "vddio1";
+ ctx->supplies[4].supply = "vddio2";
+ ctx->supplies[5].supply = "vddio18";
+ ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(ctx->supplies), ctx->supplies);
+ if (ret) {
+ dev_err_probe(dev, ret,
+ "failed to get supply regulator\n");
+ goto remove_i2c;
+ }
+
+ ctx->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_HIGH);
+ if (IS_ERR(ctx->reset_gpio)) {
+ ret = dev_err_probe(dev, PTR_ERR(ctx->reset_gpio), "failed to get reset GPIO\n");
+ goto remove_i2c;
+ }
+
+ pci_pwrctrl_init(&ctx->pwrctrl, dev);
+
+ port = TC956X_USP;
+ ret = tc956x_pwrctrl_parse_device_dt(ctx, pdev->dev.of_node, port);
+ if (ret) {
+ dev_err(dev, "failed to parse device tree properties: %d\n", ret);
+ goto remove_i2c;
+ }
+
+ /*
+ * Downstream ports are always children of the upstream port.
+ * The first node represents DSP1, the second node represents DSP2, and so on.
+ */
+ for_each_child_of_node_scoped(pdev->dev.of_node, child) {
+ ret = tc956x_pwrctrl_parse_device_dt(ctx, child, port++);
+ if (ret)
+ break;
+ /* Embedded ethernet device are under DSP3 */
+ if (port == TC956X_DSP3)
+ for_each_child_of_node_scoped(child, child1) {
+ ret = tc956x_pwrctrl_parse_device_dt(ctx, child1, port++);
+ if (ret)
+ break;
+ }
+ }
+ if (ret) {
+ dev_err(dev, "failed to parse device tree properties: %d\n", ret);
+ goto remove_i2c;
+ }
+
+ if (!pcie_is_link_active(pci_dev) && bridge->ops->stop_link)
+ bridge->ops->stop_link(pci_dev->bus);
+
+ ret = tc956x_pwrctrl_bring_up(ctx);
+ if (ret)
+ goto remove_i2c;
+
+ if (!pcie_is_link_active(pci_dev) && bridge->ops->start_link) {
+ ret = bridge->ops->start_link(pci_dev->bus);
+ if (ret)
+ goto power_off;
+ }
+
+ ret = devm_pci_pwrctrl_device_set_ready(dev, &ctx->pwrctrl);
+ if (ret)
+ goto power_off;
+
+ platform_set_drvdata(pdev, ctx);
+
+ return 0;
+
+power_off:
+ tc956x_pwrctrl_power_off(ctx);
+remove_i2c:
+ i2c_unregister_device(ctx->client);
+ i2c_put_adapter(ctx->adapter);
+ return ret;
+}
+
+static void tc956x_pwrctrl_remove(struct platform_device *pdev)
+{
+ struct tc956x_pwrctrl_ctx *ctx = platform_get_drvdata(pdev);
+
+ tc956x_pwrctrl_power_off(ctx);
+ i2c_unregister_device(ctx->client);
+ i2c_put_adapter(ctx->adapter);
+}
+
+static const struct of_device_id tc956x_pwrctrl_of_match[] = {
+ { .compatible = "pci1179,0623"},
+ { }
+};
+MODULE_DEVICE_TABLE(of, tc956x_pwrctrl_of_match);
+
+static struct platform_driver tc956x_pwrctrl_driver = {
+ .driver = {
+ .name = "pwrctrl-tc956x",
+ .of_match_table = tc956x_pwrctrl_of_match,
+ .probe_type = PROBE_PREFER_ASYNCHRONOUS,
+ },
+ .probe = tc956x_pwrctrl_probe,
+ .remove = tc956x_pwrctrl_remove,
+};
+module_platform_driver(tc956x_pwrctrl_driver);
+
+MODULE_AUTHOR("Krishna chaitanya chundru <quic_krichai@quicinc.com>");
+MODULE_DESCRIPTION("TC956x power control driver");
+MODULE_LICENSE("GPL");