162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0+
262306a36Sopenharmony_ci//
362306a36Sopenharmony_ci// Gateworks I2C PLD GPIO expander
462306a36Sopenharmony_ci//
562306a36Sopenharmony_ci// Copyright (C) 2019 Linus Walleij <linus.walleij@linaro.org>
662306a36Sopenharmony_ci//
762306a36Sopenharmony_ci// Based on code and know-how from the OpenWrt driver:
862306a36Sopenharmony_ci// Copyright (C) 2009 Gateworks Corporation
962306a36Sopenharmony_ci// Authors: Chris Lang, Imre Kaloz
1062306a36Sopenharmony_ci
1162306a36Sopenharmony_ci#include <linux/bits.h>
1262306a36Sopenharmony_ci#include <linux/kernel.h>
1362306a36Sopenharmony_ci#include <linux/slab.h>
1462306a36Sopenharmony_ci#include <linux/gpio/driver.h>
1562306a36Sopenharmony_ci#include <linux/i2c.h>
1662306a36Sopenharmony_ci#include <linux/module.h>
1762306a36Sopenharmony_ci
1862306a36Sopenharmony_ci/**
1962306a36Sopenharmony_ci * struct gw_pld - State container for Gateworks PLD
2062306a36Sopenharmony_ci * @chip: GPIO chip instance
2162306a36Sopenharmony_ci * @client: I2C client
2262306a36Sopenharmony_ci * @out: shadow register for the output bute
2362306a36Sopenharmony_ci */
2462306a36Sopenharmony_cistruct gw_pld {
2562306a36Sopenharmony_ci	struct gpio_chip chip;
2662306a36Sopenharmony_ci	struct i2c_client *client;
2762306a36Sopenharmony_ci	u8 out;
2862306a36Sopenharmony_ci};
2962306a36Sopenharmony_ci
3062306a36Sopenharmony_ci/*
3162306a36Sopenharmony_ci * The Gateworks I2C PLD chip only expose one read and one write register.
3262306a36Sopenharmony_ci * Writing a "one" bit (to match the reset state) lets that pin be used as an
3362306a36Sopenharmony_ci * input. It is an open-drain model.
3462306a36Sopenharmony_ci */
3562306a36Sopenharmony_cistatic int gw_pld_input8(struct gpio_chip *gc, unsigned offset)
3662306a36Sopenharmony_ci{
3762306a36Sopenharmony_ci	struct gw_pld *gw = gpiochip_get_data(gc);
3862306a36Sopenharmony_ci
3962306a36Sopenharmony_ci	gw->out |= BIT(offset);
4062306a36Sopenharmony_ci	return i2c_smbus_write_byte(gw->client, gw->out);
4162306a36Sopenharmony_ci}
4262306a36Sopenharmony_ci
4362306a36Sopenharmony_cistatic int gw_pld_get8(struct gpio_chip *gc, unsigned offset)
4462306a36Sopenharmony_ci{
4562306a36Sopenharmony_ci	struct gw_pld *gw = gpiochip_get_data(gc);
4662306a36Sopenharmony_ci	s32 val;
4762306a36Sopenharmony_ci
4862306a36Sopenharmony_ci	val = i2c_smbus_read_byte(gw->client);
4962306a36Sopenharmony_ci
5062306a36Sopenharmony_ci	return (val < 0) ? 0 : !!(val & BIT(offset));
5162306a36Sopenharmony_ci}
5262306a36Sopenharmony_ci
5362306a36Sopenharmony_cistatic int gw_pld_output8(struct gpio_chip *gc, unsigned offset, int value)
5462306a36Sopenharmony_ci{
5562306a36Sopenharmony_ci	struct gw_pld *gw = gpiochip_get_data(gc);
5662306a36Sopenharmony_ci
5762306a36Sopenharmony_ci	if (value)
5862306a36Sopenharmony_ci		gw->out |= BIT(offset);
5962306a36Sopenharmony_ci	else
6062306a36Sopenharmony_ci		gw->out &= ~BIT(offset);
6162306a36Sopenharmony_ci
6262306a36Sopenharmony_ci	return i2c_smbus_write_byte(gw->client, gw->out);
6362306a36Sopenharmony_ci}
6462306a36Sopenharmony_ci
6562306a36Sopenharmony_cistatic void gw_pld_set8(struct gpio_chip *gc, unsigned offset, int value)
6662306a36Sopenharmony_ci{
6762306a36Sopenharmony_ci	gw_pld_output8(gc, offset, value);
6862306a36Sopenharmony_ci}
6962306a36Sopenharmony_ci
7062306a36Sopenharmony_cistatic int gw_pld_probe(struct i2c_client *client)
7162306a36Sopenharmony_ci{
7262306a36Sopenharmony_ci	struct device *dev = &client->dev;
7362306a36Sopenharmony_ci	struct gw_pld *gw;
7462306a36Sopenharmony_ci	int ret;
7562306a36Sopenharmony_ci
7662306a36Sopenharmony_ci	gw = devm_kzalloc(dev, sizeof(*gw), GFP_KERNEL);
7762306a36Sopenharmony_ci	if (!gw)
7862306a36Sopenharmony_ci		return -ENOMEM;
7962306a36Sopenharmony_ci
8062306a36Sopenharmony_ci	gw->chip.base = -1;
8162306a36Sopenharmony_ci	gw->chip.can_sleep = true;
8262306a36Sopenharmony_ci	gw->chip.parent = dev;
8362306a36Sopenharmony_ci	gw->chip.owner = THIS_MODULE;
8462306a36Sopenharmony_ci	gw->chip.label = dev_name(dev);
8562306a36Sopenharmony_ci	gw->chip.ngpio = 8;
8662306a36Sopenharmony_ci	gw->chip.direction_input = gw_pld_input8;
8762306a36Sopenharmony_ci	gw->chip.get = gw_pld_get8;
8862306a36Sopenharmony_ci	gw->chip.direction_output = gw_pld_output8;
8962306a36Sopenharmony_ci	gw->chip.set = gw_pld_set8;
9062306a36Sopenharmony_ci	gw->client = client;
9162306a36Sopenharmony_ci
9262306a36Sopenharmony_ci	/*
9362306a36Sopenharmony_ci	 * The Gateworks I2C PLD chip does not properly send the acknowledge
9462306a36Sopenharmony_ci	 * bit at all times, but we can still use the standard i2c_smbus
9562306a36Sopenharmony_ci	 * functions by simply ignoring this bit.
9662306a36Sopenharmony_ci	 */
9762306a36Sopenharmony_ci	client->flags |= I2C_M_IGNORE_NAK;
9862306a36Sopenharmony_ci	gw->out = 0xFF;
9962306a36Sopenharmony_ci
10062306a36Sopenharmony_ci	i2c_set_clientdata(client, gw);
10162306a36Sopenharmony_ci
10262306a36Sopenharmony_ci	ret = devm_gpiochip_add_data(dev, &gw->chip, gw);
10362306a36Sopenharmony_ci	if (ret)
10462306a36Sopenharmony_ci		return ret;
10562306a36Sopenharmony_ci
10662306a36Sopenharmony_ci	dev_info(dev, "registered Gateworks PLD GPIO device\n");
10762306a36Sopenharmony_ci
10862306a36Sopenharmony_ci	return 0;
10962306a36Sopenharmony_ci}
11062306a36Sopenharmony_ci
11162306a36Sopenharmony_cistatic const struct i2c_device_id gw_pld_id[] = {
11262306a36Sopenharmony_ci	{ "gw-pld", },
11362306a36Sopenharmony_ci	{ }
11462306a36Sopenharmony_ci};
11562306a36Sopenharmony_ciMODULE_DEVICE_TABLE(i2c, gw_pld_id);
11662306a36Sopenharmony_ci
11762306a36Sopenharmony_cistatic const struct of_device_id gw_pld_dt_ids[] = {
11862306a36Sopenharmony_ci	{ .compatible = "gateworks,pld-gpio", },
11962306a36Sopenharmony_ci	{ },
12062306a36Sopenharmony_ci};
12162306a36Sopenharmony_ciMODULE_DEVICE_TABLE(of, gw_pld_dt_ids);
12262306a36Sopenharmony_ci
12362306a36Sopenharmony_cistatic struct i2c_driver gw_pld_driver = {
12462306a36Sopenharmony_ci	.driver = {
12562306a36Sopenharmony_ci		.name = "gw_pld",
12662306a36Sopenharmony_ci		.of_match_table = gw_pld_dt_ids,
12762306a36Sopenharmony_ci	},
12862306a36Sopenharmony_ci	.probe = gw_pld_probe,
12962306a36Sopenharmony_ci	.id_table = gw_pld_id,
13062306a36Sopenharmony_ci};
13162306a36Sopenharmony_cimodule_i2c_driver(gw_pld_driver);
13262306a36Sopenharmony_ci
13362306a36Sopenharmony_ciMODULE_LICENSE("GPL");
13462306a36Sopenharmony_ciMODULE_AUTHOR("Linus Walleij <linus.walleij@linaro.org>");
135