cachepc-linux

Fork of AMDESE/linux with modifications for CachePC side-channel attack
git clone https://git.sinitax.com/sinitax/cachepc-linux
Log | Files | Refs | README | LICENSE | sfeed.txt

i2c-mux-ltc4306.c (7798B)


      1// SPDX-License-Identifier: GPL-2.0-only
      2/*
      3 * Linear Technology LTC4306 and LTC4305 I2C multiplexer/switch
      4 *
      5 * Copyright (C) 2017 Analog Devices Inc.
      6 *
      7 * Based on: i2c-mux-pca954x.c
      8 *
      9 * Datasheet: http://cds.linear.com/docs/en/datasheet/4306.pdf
     10 */
     11
     12#include <linux/gpio/consumer.h>
     13#include <linux/gpio/driver.h>
     14#include <linux/i2c-mux.h>
     15#include <linux/i2c.h>
     16#include <linux/module.h>
     17#include <linux/of.h>
     18#include <linux/of_device.h>
     19#include <linux/property.h>
     20#include <linux/regmap.h>
     21#include <linux/slab.h>
     22
     23#define LTC4305_MAX_NCHANS 2
     24#define LTC4306_MAX_NCHANS 4
     25
     26#define LTC_REG_STATUS	0x0
     27#define LTC_REG_CONFIG	0x1
     28#define LTC_REG_MODE	0x2
     29#define LTC_REG_SWITCH	0x3
     30
     31#define LTC_DOWNSTREAM_ACCL_EN	BIT(6)
     32#define LTC_UPSTREAM_ACCL_EN	BIT(7)
     33
     34#define LTC_GPIO_ALL_INPUT	0xC0
     35#define LTC_SWITCH_MASK		0xF0
     36
     37enum ltc_type {
     38	ltc_4305,
     39	ltc_4306,
     40};
     41
     42struct chip_desc {
     43	u8 nchans;
     44	u8 num_gpios;
     45};
     46
     47struct ltc4306 {
     48	struct regmap *regmap;
     49	struct gpio_chip gpiochip;
     50	const struct chip_desc *chip;
     51};
     52
     53static const struct chip_desc chips[] = {
     54	[ltc_4305] = {
     55		.nchans = LTC4305_MAX_NCHANS,
     56	},
     57	[ltc_4306] = {
     58		.nchans = LTC4306_MAX_NCHANS,
     59		.num_gpios = 2,
     60	},
     61};
     62
     63static bool ltc4306_is_volatile_reg(struct device *dev, unsigned int reg)
     64{
     65	return (reg == LTC_REG_CONFIG) ? true : false;
     66}
     67
     68static const struct regmap_config ltc4306_regmap_config = {
     69	.reg_bits = 8,
     70	.val_bits = 8,
     71	.max_register = LTC_REG_SWITCH,
     72	.volatile_reg = ltc4306_is_volatile_reg,
     73	.cache_type = REGCACHE_FLAT,
     74};
     75
     76static int ltc4306_gpio_get(struct gpio_chip *chip, unsigned int offset)
     77{
     78	struct ltc4306 *data = gpiochip_get_data(chip);
     79	unsigned int val;
     80	int ret;
     81
     82	ret = regmap_read(data->regmap, LTC_REG_CONFIG, &val);
     83	if (ret < 0)
     84		return ret;
     85
     86	return !!(val & BIT(1 - offset));
     87}
     88
     89static void ltc4306_gpio_set(struct gpio_chip *chip, unsigned int offset,
     90			     int value)
     91{
     92	struct ltc4306 *data = gpiochip_get_data(chip);
     93
     94	regmap_update_bits(data->regmap, LTC_REG_CONFIG, BIT(5 - offset),
     95			   value ? BIT(5 - offset) : 0);
     96}
     97
     98static int ltc4306_gpio_get_direction(struct gpio_chip *chip,
     99				      unsigned int offset)
    100{
    101	struct ltc4306 *data = gpiochip_get_data(chip);
    102	unsigned int val;
    103	int ret;
    104
    105	ret = regmap_read(data->regmap, LTC_REG_MODE, &val);
    106	if (ret < 0)
    107		return ret;
    108
    109	return !!(val & BIT(7 - offset));
    110}
    111
    112static int ltc4306_gpio_direction_input(struct gpio_chip *chip,
    113					unsigned int offset)
    114{
    115	struct ltc4306 *data = gpiochip_get_data(chip);
    116
    117	return regmap_update_bits(data->regmap, LTC_REG_MODE,
    118				  BIT(7 - offset), BIT(7 - offset));
    119}
    120
    121static int ltc4306_gpio_direction_output(struct gpio_chip *chip,
    122					 unsigned int offset, int value)
    123{
    124	struct ltc4306 *data = gpiochip_get_data(chip);
    125
    126	ltc4306_gpio_set(chip, offset, value);
    127	return regmap_update_bits(data->regmap, LTC_REG_MODE,
    128				  BIT(7 - offset), 0);
    129}
    130
    131static int ltc4306_gpio_set_config(struct gpio_chip *chip,
    132				   unsigned int offset, unsigned long config)
    133{
    134	struct ltc4306 *data = gpiochip_get_data(chip);
    135	unsigned int val;
    136
    137	switch (pinconf_to_config_param(config)) {
    138	case PIN_CONFIG_DRIVE_OPEN_DRAIN:
    139		val = 0;
    140		break;
    141	case PIN_CONFIG_DRIVE_PUSH_PULL:
    142		val = BIT(4 - offset);
    143		break;
    144	default:
    145		return -ENOTSUPP;
    146	}
    147
    148	return regmap_update_bits(data->regmap, LTC_REG_MODE,
    149				  BIT(4 - offset), val);
    150}
    151
    152static int ltc4306_gpio_init(struct ltc4306 *data)
    153{
    154	struct device *dev = regmap_get_device(data->regmap);
    155
    156	if (!data->chip->num_gpios)
    157		return 0;
    158
    159	data->gpiochip.label = dev_name(dev);
    160	data->gpiochip.base = -1;
    161	data->gpiochip.ngpio = data->chip->num_gpios;
    162	data->gpiochip.parent = dev;
    163	data->gpiochip.can_sleep = true;
    164	data->gpiochip.get_direction = ltc4306_gpio_get_direction;
    165	data->gpiochip.direction_input = ltc4306_gpio_direction_input;
    166	data->gpiochip.direction_output = ltc4306_gpio_direction_output;
    167	data->gpiochip.get = ltc4306_gpio_get;
    168	data->gpiochip.set = ltc4306_gpio_set;
    169	data->gpiochip.set_config = ltc4306_gpio_set_config;
    170	data->gpiochip.owner = THIS_MODULE;
    171
    172	/* gpiolib assumes all GPIOs default input */
    173	regmap_write(data->regmap, LTC_REG_MODE, LTC_GPIO_ALL_INPUT);
    174
    175	return devm_gpiochip_add_data(dev, &data->gpiochip, data);
    176}
    177
    178static int ltc4306_select_mux(struct i2c_mux_core *muxc, u32 chan)
    179{
    180	struct ltc4306 *data = i2c_mux_priv(muxc);
    181
    182	return regmap_update_bits(data->regmap, LTC_REG_SWITCH,
    183				  LTC_SWITCH_MASK, BIT(7 - chan));
    184}
    185
    186static int ltc4306_deselect_mux(struct i2c_mux_core *muxc, u32 chan)
    187{
    188	struct ltc4306 *data = i2c_mux_priv(muxc);
    189
    190	return regmap_update_bits(data->regmap, LTC_REG_SWITCH,
    191				  LTC_SWITCH_MASK, 0);
    192}
    193
    194static const struct i2c_device_id ltc4306_id[] = {
    195	{ "ltc4305", ltc_4305 },
    196	{ "ltc4306", ltc_4306 },
    197	{ }
    198};
    199MODULE_DEVICE_TABLE(i2c, ltc4306_id);
    200
    201static const struct of_device_id ltc4306_of_match[] = {
    202	{ .compatible = "lltc,ltc4305", .data = &chips[ltc_4305] },
    203	{ .compatible = "lltc,ltc4306", .data = &chips[ltc_4306] },
    204	{ }
    205};
    206MODULE_DEVICE_TABLE(of, ltc4306_of_match);
    207
    208static int ltc4306_probe(struct i2c_client *client)
    209{
    210	struct i2c_adapter *adap = client->adapter;
    211	const struct chip_desc *chip;
    212	struct i2c_mux_core *muxc;
    213	struct ltc4306 *data;
    214	struct gpio_desc *gpio;
    215	bool idle_disc;
    216	unsigned int val = 0;
    217	int num, ret;
    218
    219	chip = of_device_get_match_data(&client->dev);
    220
    221	if (!chip)
    222		chip = &chips[i2c_match_id(ltc4306_id, client)->driver_data];
    223
    224	idle_disc = device_property_read_bool(&client->dev,
    225					      "i2c-mux-idle-disconnect");
    226
    227	muxc = i2c_mux_alloc(adap, &client->dev,
    228			     chip->nchans, sizeof(*data),
    229			     I2C_MUX_LOCKED, ltc4306_select_mux,
    230			     idle_disc ? ltc4306_deselect_mux : NULL);
    231	if (!muxc)
    232		return -ENOMEM;
    233	data = i2c_mux_priv(muxc);
    234	data->chip = chip;
    235
    236	i2c_set_clientdata(client, muxc);
    237
    238	data->regmap = devm_regmap_init_i2c(client, &ltc4306_regmap_config);
    239	if (IS_ERR(data->regmap)) {
    240		ret = PTR_ERR(data->regmap);
    241		dev_err(&client->dev, "Failed to allocate register map: %d\n",
    242			ret);
    243		return ret;
    244	}
    245
    246	/* Reset and enable the mux if an enable GPIO is specified. */
    247	gpio = devm_gpiod_get_optional(&client->dev, "enable", GPIOD_OUT_LOW);
    248	if (IS_ERR(gpio))
    249		return PTR_ERR(gpio);
    250
    251	if (gpio) {
    252		udelay(1);
    253		gpiod_set_value(gpio, 1);
    254	}
    255
    256	/*
    257	 * Write the mux register at addr to verify
    258	 * that the mux is in fact present. This also
    259	 * initializes the mux to disconnected state.
    260	 */
    261	if (regmap_write(data->regmap, LTC_REG_SWITCH, 0) < 0) {
    262		dev_warn(&client->dev, "probe failed\n");
    263		return -ENODEV;
    264	}
    265
    266	if (device_property_read_bool(&client->dev,
    267				      "ltc,downstream-accelerators-enable"))
    268		val |= LTC_DOWNSTREAM_ACCL_EN;
    269
    270	if (device_property_read_bool(&client->dev,
    271				      "ltc,upstream-accelerators-enable"))
    272		val |= LTC_UPSTREAM_ACCL_EN;
    273
    274	if (regmap_write(data->regmap, LTC_REG_CONFIG, val) < 0)
    275		return -ENODEV;
    276
    277	ret = ltc4306_gpio_init(data);
    278	if (ret < 0)
    279		return ret;
    280
    281	/* Now create an adapter for each channel */
    282	for (num = 0; num < chip->nchans; num++) {
    283		ret = i2c_mux_add_adapter(muxc, 0, num, 0);
    284		if (ret) {
    285			i2c_mux_del_adapters(muxc);
    286			return ret;
    287		}
    288	}
    289
    290	dev_info(&client->dev,
    291		 "registered %d multiplexed busses for I2C switch %s\n",
    292		 num, client->name);
    293
    294	return 0;
    295}
    296
    297static int ltc4306_remove(struct i2c_client *client)
    298{
    299	struct i2c_mux_core *muxc = i2c_get_clientdata(client);
    300
    301	i2c_mux_del_adapters(muxc);
    302
    303	return 0;
    304}
    305
    306static struct i2c_driver ltc4306_driver = {
    307	.driver		= {
    308		.name	= "ltc4306",
    309		.of_match_table = of_match_ptr(ltc4306_of_match),
    310	},
    311	.probe_new	= ltc4306_probe,
    312	.remove		= ltc4306_remove,
    313	.id_table	= ltc4306_id,
    314};
    315
    316module_i2c_driver(ltc4306_driver);
    317
    318MODULE_AUTHOR("Michael Hennerich <michael.hennerich@analog.com>");
    319MODULE_DESCRIPTION("Linear Technology LTC4306, LTC4305 I2C mux/switch driver");
    320MODULE_LICENSE("GPL v2");