root/drivers/usb/typec/mux/nb7vpq904m.c
// SPDX-License-Identifier: GPL-2.0+
/*
 * OnSemi NB7VPQ904M Type-C driver
 *
 * Copyright (C) 2023 Dmitry Baryshkov <dmitry.baryshkov@linaro.org>
 */
#include <linux/i2c.h>
#include <linux/mutex.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/regmap.h>
#include <linux/bitfield.h>
#include <linux/of_graph.h>
#include <drm/bridge/aux-bridge.h>
#include <linux/usb/typec_dp.h>
#include <linux/usb/typec_mux.h>
#include <linux/usb/typec_retimer.h>
#include <linux/gpio/consumer.h>
#include <linux/regulator/consumer.h>

#define NB7_CHNA                0
#define NB7_CHNB                1
#define NB7_CHNC                2
#define NB7_CHND                3
#define NB7_IS_CHAN_AD(channel) (channel == NB7_CHNA || channel == NB7_CHND)

#define GEN_DEV_SET_REG                 0x00

#define GEN_DEV_SET_CHIP_EN             BIT(0)
#define GEN_DEV_SET_CHNA_EN             BIT(4)
#define GEN_DEV_SET_CHNB_EN             BIT(5)
#define GEN_DEV_SET_CHNC_EN             BIT(6)
#define GEN_DEV_SET_CHND_EN             BIT(7)

#define GEN_DEV_SET_OP_MODE_MASK        GENMASK(3, 1)

#define GEN_DEV_SET_OP_MODE_DP_CC2      0
#define GEN_DEV_SET_OP_MODE_DP_CC1      1
#define GEN_DEV_SET_OP_MODE_DP_4LANE    2
#define GEN_DEV_SET_OP_MODE_USB         5

#define EQ_SETTING_REG_BASE             0x01
#define EQ_SETTING_REG(n)               (EQ_SETTING_REG_BASE + (n) * 2)
#define EQ_SETTING_MASK                 GENMASK(3, 1)

#define OUTPUT_COMPRESSION_AND_POL_REG_BASE     0x02
#define OUTPUT_COMPRESSION_AND_POL_REG(n)       (OUTPUT_COMPRESSION_AND_POL_REG_BASE + (n) * 2)
#define OUTPUT_COMPRESSION_MASK         GENMASK(2, 1)

#define FLAT_GAIN_REG_BASE              0x18
#define FLAT_GAIN_REG(n)                (FLAT_GAIN_REG_BASE + (n) * 2)
#define FLAT_GAIN_MASK                  GENMASK(1, 0)

#define LOSS_MATCH_REG_BASE             0x19
#define LOSS_MATCH_REG(n)               (LOSS_MATCH_REG_BASE + (n) * 2)
#define LOSS_MATCH_MASK                 GENMASK(1, 0)

#define AUX_CC_REG                      0x09

#define CHIP_VERSION_REG                0x17

struct nb7vpq904m {
        struct i2c_client *client;
        struct gpio_desc *enable_gpio;
        struct regulator *vcc_supply;
        struct regmap *regmap;
        struct typec_switch_dev *sw;
        struct typec_retimer *retimer;

        bool swap_data_lanes;
        struct typec_switch *typec_switch;
        struct typec_mux *typec_mux;

        struct mutex lock; /* protect non-concurrent retimer & switch */

        enum typec_orientation orientation;
        unsigned long mode;
        unsigned int svid;
};

static void nb7vpq904m_set_channel(struct nb7vpq904m *nb7, unsigned int channel, bool dp)
{
        u8 eq, out_comp, flat_gain, loss_match;

        if (dp) {
                eq = NB7_IS_CHAN_AD(channel) ? 0x6 : 0x4;
                out_comp = 0x3;
                flat_gain = NB7_IS_CHAN_AD(channel) ? 0x2 : 0x1;
                loss_match = 0x3;
        } else {
                eq = 0x4;
                out_comp = 0x3;
                flat_gain = NB7_IS_CHAN_AD(channel) ? 0x3 : 0x1;
                loss_match = NB7_IS_CHAN_AD(channel) ? 0x1 : 0x3;
        }

        regmap_update_bits(nb7->regmap, EQ_SETTING_REG(channel),
                           EQ_SETTING_MASK, FIELD_PREP(EQ_SETTING_MASK, eq));
        regmap_update_bits(nb7->regmap, OUTPUT_COMPRESSION_AND_POL_REG(channel),
                           OUTPUT_COMPRESSION_MASK, FIELD_PREP(OUTPUT_COMPRESSION_MASK, out_comp));
        regmap_update_bits(nb7->regmap, FLAT_GAIN_REG(channel),
                           FLAT_GAIN_MASK, FIELD_PREP(FLAT_GAIN_MASK, flat_gain));
        regmap_update_bits(nb7->regmap, LOSS_MATCH_REG(channel),
                           LOSS_MATCH_MASK, FIELD_PREP(LOSS_MATCH_MASK, loss_match));
}

static int nb7vpq904m_set(struct nb7vpq904m *nb7)
{
        bool reverse = (nb7->orientation == TYPEC_ORIENTATION_REVERSE);

        switch (nb7->mode) {
        case TYPEC_STATE_SAFE:
                regmap_write(nb7->regmap, GEN_DEV_SET_REG,
                             GEN_DEV_SET_CHIP_EN |
                             GEN_DEV_SET_CHNA_EN |
                             GEN_DEV_SET_CHNB_EN |
                             GEN_DEV_SET_CHNC_EN |
                             GEN_DEV_SET_CHND_EN |
                             FIELD_PREP(GEN_DEV_SET_OP_MODE_MASK,
                                        GEN_DEV_SET_OP_MODE_USB));
                nb7vpq904m_set_channel(nb7, NB7_CHNA, false);
                nb7vpq904m_set_channel(nb7, NB7_CHNB, false);
                nb7vpq904m_set_channel(nb7, NB7_CHNC, false);
                nb7vpq904m_set_channel(nb7, NB7_CHND, false);
                regmap_write(nb7->regmap, AUX_CC_REG, 0x2);

                return 0;

        case TYPEC_STATE_USB:
                /*
                 * Normal Orientation (CC1)
                 * A -> USB RX
                 * B -> USB TX
                 * C -> X
                 * D -> X
                 * Flipped Orientation (CC2)
                 * A -> X
                 * B -> X
                 * C -> USB TX
                 * D -> USB RX
                 *
                 * Reversed if data lanes are swapped
                 */
                if (reverse ^ nb7->swap_data_lanes) {
                        regmap_write(nb7->regmap, GEN_DEV_SET_REG,
                                     GEN_DEV_SET_CHIP_EN |
                                     GEN_DEV_SET_CHNA_EN |
                                     GEN_DEV_SET_CHNB_EN |
                                     FIELD_PREP(GEN_DEV_SET_OP_MODE_MASK,
                                                GEN_DEV_SET_OP_MODE_USB));
                        nb7vpq904m_set_channel(nb7, NB7_CHNA, false);
                        nb7vpq904m_set_channel(nb7, NB7_CHNB, false);
                } else {
                        regmap_write(nb7->regmap, GEN_DEV_SET_REG,
                                     GEN_DEV_SET_CHIP_EN |
                                     GEN_DEV_SET_CHNC_EN |
                                     GEN_DEV_SET_CHND_EN |
                                     FIELD_PREP(GEN_DEV_SET_OP_MODE_MASK,
                                                GEN_DEV_SET_OP_MODE_USB));
                        nb7vpq904m_set_channel(nb7, NB7_CHNC, false);
                        nb7vpq904m_set_channel(nb7, NB7_CHND, false);
                }
                regmap_write(nb7->regmap, AUX_CC_REG, 0x2);

                return 0;

        default:
                if (nb7->svid != USB_TYPEC_DP_SID)
                        return -EINVAL;

                break;
        }

        /* DP Altmode Setup */

        regmap_write(nb7->regmap, AUX_CC_REG, reverse ? 0x1 : 0x0);

        switch (nb7->mode) {
        case TYPEC_DP_STATE_C:
        case TYPEC_DP_STATE_E:
                /*
                 * Normal Orientation (CC1)
                 * A -> DP3
                 * B -> DP2
                 * C -> DP1
                 * D -> DP0
                 * Flipped Orientation (CC2)
                 * A -> DP0
                 * B -> DP1
                 * C -> DP2
                 * D -> DP3
                 */
                regmap_write(nb7->regmap, GEN_DEV_SET_REG,
                             GEN_DEV_SET_CHIP_EN |
                             GEN_DEV_SET_CHNA_EN |
                             GEN_DEV_SET_CHNB_EN |
                             GEN_DEV_SET_CHNC_EN |
                             GEN_DEV_SET_CHND_EN |
                             FIELD_PREP(GEN_DEV_SET_OP_MODE_MASK,
                                        GEN_DEV_SET_OP_MODE_DP_4LANE));
                nb7vpq904m_set_channel(nb7, NB7_CHNA, true);
                nb7vpq904m_set_channel(nb7, NB7_CHNB, true);
                nb7vpq904m_set_channel(nb7, NB7_CHNC, true);
                nb7vpq904m_set_channel(nb7, NB7_CHND, true);
                break;

        case TYPEC_DP_STATE_D:
        case TYPEC_DP_STATE_F:
                regmap_write(nb7->regmap, GEN_DEV_SET_REG,
                             GEN_DEV_SET_CHIP_EN |
                             GEN_DEV_SET_CHNA_EN |
                             GEN_DEV_SET_CHNB_EN |
                             GEN_DEV_SET_CHNC_EN |
                             GEN_DEV_SET_CHND_EN |
                             FIELD_PREP(GEN_DEV_SET_OP_MODE_MASK,
                                        reverse ^ nb7->swap_data_lanes ?
                                                GEN_DEV_SET_OP_MODE_DP_CC2
                                                : GEN_DEV_SET_OP_MODE_DP_CC1));

                /*
                 * Normal Orientation (CC1)
                 * A -> USB RX
                 * B -> USB TX
                 * C -> DP1
                 * D -> DP0
                 * Flipped Orientation (CC2)
                 * A -> DP0
                 * B -> DP1
                 * C -> USB TX
                 * D -> USB RX
                 *
                 * Reversed if data lanes are swapped
                 */
                if (nb7->swap_data_lanes) {
                        nb7vpq904m_set_channel(nb7, NB7_CHNA, !reverse);
                        nb7vpq904m_set_channel(nb7, NB7_CHNB, !reverse);
                        nb7vpq904m_set_channel(nb7, NB7_CHNC, reverse);
                        nb7vpq904m_set_channel(nb7, NB7_CHND, reverse);
                } else {
                        nb7vpq904m_set_channel(nb7, NB7_CHNA, reverse);
                        nb7vpq904m_set_channel(nb7, NB7_CHNB, reverse);
                        nb7vpq904m_set_channel(nb7, NB7_CHNC, !reverse);
                        nb7vpq904m_set_channel(nb7, NB7_CHND, !reverse);
                }
                break;

        default:
                return -EOPNOTSUPP;
        }

        return 0;
}

static int nb7vpq904m_sw_set(struct typec_switch_dev *sw, enum typec_orientation orientation)
{
        struct nb7vpq904m *nb7 = typec_switch_get_drvdata(sw);
        int ret;

        ret = typec_switch_set(nb7->typec_switch, orientation);
        if (ret)
                return ret;

        mutex_lock(&nb7->lock);

        if (nb7->orientation != orientation) {
                nb7->orientation = orientation;

                ret = nb7vpq904m_set(nb7);
        }

        mutex_unlock(&nb7->lock);

        return ret;
}

static int nb7vpq904m_retimer_set(struct typec_retimer *retimer, struct typec_retimer_state *state)
{
        struct nb7vpq904m *nb7 = typec_retimer_get_drvdata(retimer);
        struct typec_mux_state mux_state;
        int ret = 0;

        mutex_lock(&nb7->lock);

        if (nb7->mode != state->mode) {
                nb7->mode = state->mode;

                if (state->alt)
                        nb7->svid = state->alt->svid;
                else
                        nb7->svid = 0; // No SVID

                ret = nb7vpq904m_set(nb7);
        }

        mutex_unlock(&nb7->lock);

        if (ret)
                return ret;

        mux_state.alt = state->alt;
        mux_state.data = state->data;
        mux_state.mode = state->mode;

        return typec_mux_set(nb7->typec_mux, &mux_state);
}

static const struct regmap_config nb7_regmap = {
        .max_register = 0x1f,
        .reg_bits = 8,
        .val_bits = 8,
};

enum {
        NORMAL_LANE_MAPPING,
        INVERT_LANE_MAPPING,
};

#define DATA_LANES_COUNT        4

static const int supported_data_lane_mapping[][DATA_LANES_COUNT] = {
        [NORMAL_LANE_MAPPING] = { 0, 1, 2, 3 },
        [INVERT_LANE_MAPPING] = { 3, 2, 1, 0 },
};

static int nb7vpq904m_parse_data_lanes_mapping(struct nb7vpq904m *nb7)
{
        struct device_node *ep;
        u32 data_lanes[4];
        int ret, i, j;

        ep = of_graph_get_endpoint_by_regs(nb7->client->dev.of_node, 1, 0);

        if (!ep)
                return 0;


        ret = of_property_count_u32_elems(ep, "data-lanes");
        if (ret == -EINVAL)
                /* Property isn't here, consider default mapping */
                goto out_done;
        if (ret < 0)
                goto out_error;

        if (ret != DATA_LANES_COUNT) {
                dev_err(&nb7->client->dev, "expected 4 data lanes\n");
                ret = -EINVAL;
                goto out_error;
        }

        ret = of_property_read_u32_array(ep, "data-lanes", data_lanes, DATA_LANES_COUNT);
        if (ret)
                goto out_error;

        for (i = 0; i < ARRAY_SIZE(supported_data_lane_mapping); i++) {
                for (j = 0; j < DATA_LANES_COUNT; j++) {
                        if (data_lanes[j] != supported_data_lane_mapping[i][j])
                                break;
                }

                if (j == DATA_LANES_COUNT)
                        break;
        }

        switch (i) {
        case NORMAL_LANE_MAPPING:
                break;
        case INVERT_LANE_MAPPING:
                nb7->swap_data_lanes = true;
                dev_info(&nb7->client->dev, "using inverted data lanes mapping\n");
                break;
        default:
                dev_err(&nb7->client->dev, "invalid data lanes mapping\n");
                ret = -EINVAL;
                goto out_error;
        }

out_done:
        ret = 0;

out_error:
        of_node_put(ep);

        return ret;
}

static int nb7vpq904m_probe(struct i2c_client *client)
{
        struct device *dev = &client->dev;
        struct typec_switch_desc sw_desc = { };
        struct typec_retimer_desc retimer_desc = { };
        struct nb7vpq904m *nb7;
        int ret;

        nb7 = devm_kzalloc(dev, sizeof(*nb7), GFP_KERNEL);
        if (!nb7)
                return -ENOMEM;

        nb7->client = client;

        nb7->regmap = devm_regmap_init_i2c(client, &nb7_regmap);
        if (IS_ERR(nb7->regmap)) {
                dev_err(&client->dev, "Failed to allocate register map\n");
                return PTR_ERR(nb7->regmap);
        }

        nb7->mode = TYPEC_STATE_SAFE;
        nb7->orientation = TYPEC_ORIENTATION_NONE;

        mutex_init(&nb7->lock);

        nb7->enable_gpio = devm_gpiod_get_optional(dev, "enable", GPIOD_OUT_LOW);
        if (IS_ERR(nb7->enable_gpio))
                return dev_err_probe(dev, PTR_ERR(nb7->enable_gpio),
                                     "unable to acquire enable gpio\n");

        nb7->vcc_supply = devm_regulator_get_optional(dev, "vcc");
        if (IS_ERR(nb7->vcc_supply))
                return PTR_ERR(nb7->vcc_supply);

        nb7->typec_switch = fwnode_typec_switch_get(dev->fwnode);
        if (IS_ERR(nb7->typec_switch))
                return dev_err_probe(dev, PTR_ERR(nb7->typec_switch),
                                     "failed to acquire orientation-switch\n");

        nb7->typec_mux = fwnode_typec_mux_get(dev->fwnode);
        if (IS_ERR(nb7->typec_mux)) {
                ret = dev_err_probe(dev, PTR_ERR(nb7->typec_mux),
                                    "Failed to acquire mode-switch\n");
                goto err_switch_put;
        }

        ret = nb7vpq904m_parse_data_lanes_mapping(nb7);
        if (ret)
                goto err_mux_put;

        ret = regulator_enable(nb7->vcc_supply);
        if (ret)
                dev_warn(dev, "Failed to enable vcc: %d\n", ret);

        gpiod_set_value(nb7->enable_gpio, 1);

        ret = drm_aux_bridge_register(dev);
        if (ret)
                goto err_disable_gpio;

        sw_desc.drvdata = nb7;
        sw_desc.fwnode = dev->fwnode;
        sw_desc.set = nb7vpq904m_sw_set;

        nb7->sw = typec_switch_register(dev, &sw_desc);
        if (IS_ERR(nb7->sw)) {
                ret = dev_err_probe(dev, PTR_ERR(nb7->sw),
                                    "Error registering typec switch\n");
                goto err_disable_gpio;
        }

        retimer_desc.drvdata = nb7;
        retimer_desc.fwnode = dev->fwnode;
        retimer_desc.set = nb7vpq904m_retimer_set;

        nb7->retimer = typec_retimer_register(dev, &retimer_desc);
        if (IS_ERR(nb7->retimer)) {
                ret = dev_err_probe(dev, PTR_ERR(nb7->retimer),
                                    "Error registering typec retimer\n");
                goto err_switch_unregister;
        }

        return 0;

err_switch_unregister:
        typec_switch_unregister(nb7->sw);

err_disable_gpio:
        gpiod_set_value(nb7->enable_gpio, 0);
        regulator_disable(nb7->vcc_supply);

err_mux_put:
        typec_mux_put(nb7->typec_mux);

err_switch_put:
        typec_switch_put(nb7->typec_switch);

        return ret;
}

static void nb7vpq904m_remove(struct i2c_client *client)
{
        struct nb7vpq904m *nb7 = i2c_get_clientdata(client);

        typec_retimer_unregister(nb7->retimer);
        typec_switch_unregister(nb7->sw);

        gpiod_set_value(nb7->enable_gpio, 0);

        regulator_disable(nb7->vcc_supply);

        typec_mux_put(nb7->typec_mux);
        typec_switch_put(nb7->typec_switch);
}

static const struct i2c_device_id nb7vpq904m_table[] = {
        { "nb7vpq904m" },
        { }
};
MODULE_DEVICE_TABLE(i2c, nb7vpq904m_table);

static const struct of_device_id nb7vpq904m_of_table[] = {
        { .compatible = "onnn,nb7vpq904m" },
        { }
};
MODULE_DEVICE_TABLE(of, nb7vpq904m_of_table);

static struct i2c_driver nb7vpq904m_driver = {
        .driver = {
                .name = "nb7vpq904m",
                .of_match_table = nb7vpq904m_of_table,
        },
        .probe          = nb7vpq904m_probe,
        .remove         = nb7vpq904m_remove,
        .id_table       = nb7vpq904m_table,
};

module_i2c_driver(nb7vpq904m_driver);

MODULE_AUTHOR("Dmitry Baryshkov <dmitry.baryshkov@linaro.org>");
MODULE_DESCRIPTION("OnSemi NB7VPQ904M Type-C driver");
MODULE_LICENSE("GPL");