-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
mfd: Add Gateworks System Controller core driver
The Gateworks System Controller (GSC) is an I2C slave controller implemented with an MSP430 micro-controller whose firmware embeds the following features: - I/O expander (16 GPIO's) using PCA955x protocol - Real Time Clock using DS1672 protocol - User EEPROM using AT24 protocol - HWMON using custom protocol - Interrupt controller with tamper detect, user pushbotton - Watchdog controller capable of full board power-cycle - Power Control capable of full board power-cycle see http://trac.gateworks.com/wiki/gsc for more details Signed-off-by: Tim Harvey <tharvey@gateworks.com> Signed-off-by: Lee Jones <lee.jones@linaro.org>
- Loading branch information
Tim Harvey
authored and
Lee Jones
committed
May 20, 2020
1 parent
3e6cae8
commit d852349
Showing
5 changed files
with
377 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,277 @@ | ||
// SPDX-License-Identifier: GPL-2.0 | ||
/* | ||
* The Gateworks System Controller (GSC) is a multi-function | ||
* device designed for use in Gateworks Single Board Computers. | ||
* The control interface is I2C, with an interrupt. The device supports | ||
* system functions such as push-button monitoring, multiple ADC's for | ||
* voltage and temperature monitoring, fan controller and watchdog monitor. | ||
* | ||
* Copyright (C) 2020 Gateworks Corporation | ||
*/ | ||
|
||
#include <linux/device.h> | ||
#include <linux/i2c.h> | ||
#include <linux/interrupt.h> | ||
#include <linux/mfd/gsc.h> | ||
#include <linux/module.h> | ||
#include <linux/mutex.h> | ||
#include <linux/of.h> | ||
#include <linux/of_platform.h> | ||
#include <linux/platform_device.h> | ||
#include <linux/regmap.h> | ||
|
||
#include <asm/unaligned.h> | ||
|
||
/* | ||
* The GSC suffers from an errata where occasionally during | ||
* ADC cycles the chip can NAK I2C transactions. To ensure we have reliable | ||
* register access we place retries around register access. | ||
*/ | ||
#define I2C_RETRIES 3 | ||
|
||
int gsc_write(void *context, unsigned int reg, unsigned int val) | ||
{ | ||
struct i2c_client *client = context; | ||
int retry, ret; | ||
|
||
for (retry = 0; retry < I2C_RETRIES; retry++) { | ||
ret = i2c_smbus_write_byte_data(client, reg, val); | ||
/* | ||
* -EAGAIN returned when the i2c host controller is busy | ||
* -EIO returned when i2c device is busy | ||
*/ | ||
if (ret != -EAGAIN && ret != -EIO) | ||
break; | ||
} | ||
|
||
return 0; | ||
} | ||
EXPORT_SYMBOL_GPL(gsc_write); | ||
|
||
int gsc_read(void *context, unsigned int reg, unsigned int *val) | ||
{ | ||
struct i2c_client *client = context; | ||
int retry, ret; | ||
|
||
for (retry = 0; retry < I2C_RETRIES; retry++) { | ||
ret = i2c_smbus_read_byte_data(client, reg); | ||
/* | ||
* -EAGAIN returned when the i2c host controller is busy | ||
* -EIO returned when i2c device is busy | ||
*/ | ||
if (ret != -EAGAIN && ret != -EIO) | ||
break; | ||
} | ||
*val = ret & 0xff; | ||
|
||
return 0; | ||
} | ||
EXPORT_SYMBOL_GPL(gsc_read); | ||
|
||
/* | ||
* gsc_powerdown - API to use GSC to power down board for a specific time | ||
* | ||
* secs - number of seconds to remain powered off | ||
*/ | ||
static int gsc_powerdown(struct gsc_dev *gsc, unsigned long secs) | ||
{ | ||
int ret; | ||
unsigned char regs[4]; | ||
|
||
dev_info(&gsc->i2c->dev, "GSC powerdown for %ld seconds\n", | ||
secs); | ||
|
||
put_unaligned_le32(secs, regs); | ||
ret = regmap_bulk_write(gsc->regmap, GSC_TIME_ADD, regs, 4); | ||
if (ret) | ||
return ret; | ||
|
||
ret = regmap_update_bits(gsc->regmap, GSC_CTRL_1, | ||
BIT(GSC_CTRL_1_SLEEP_ADD), | ||
BIT(GSC_CTRL_1_SLEEP_ADD)); | ||
if (ret) | ||
return ret; | ||
|
||
ret = regmap_update_bits(gsc->regmap, GSC_CTRL_1, | ||
BIT(GSC_CTRL_1_SLEEP_ACTIVATE) | | ||
BIT(GSC_CTRL_1_SLEEP_ENABLE), | ||
BIT(GSC_CTRL_1_SLEEP_ACTIVATE) | | ||
BIT(GSC_CTRL_1_SLEEP_ENABLE)); | ||
|
||
|
||
return ret; | ||
} | ||
|
||
static ssize_t gsc_show(struct device *dev, struct device_attribute *attr, | ||
char *buf) | ||
{ | ||
struct gsc_dev *gsc = dev_get_drvdata(dev); | ||
const char *name = attr->attr.name; | ||
int rz = 0; | ||
|
||
if (strcasecmp(name, "fw_version") == 0) | ||
rz = sprintf(buf, "%d\n", gsc->fwver); | ||
else if (strcasecmp(name, "fw_crc") == 0) | ||
rz = sprintf(buf, "0x%04x\n", gsc->fwcrc); | ||
else | ||
dev_err(dev, "invalid command: '%s'\n", name); | ||
|
||
return rz; | ||
} | ||
|
||
static ssize_t gsc_store(struct device *dev, struct device_attribute *attr, | ||
const char *buf, size_t count) | ||
{ | ||
struct gsc_dev *gsc = dev_get_drvdata(dev); | ||
const char *name = attr->attr.name; | ||
long value; | ||
|
||
if (strcasecmp(name, "powerdown") == 0) { | ||
if (kstrtol(buf, 0, &value) == 0) | ||
gsc_powerdown(gsc, value); | ||
} else { | ||
dev_err(dev, "invalid command: '%s\n", name); | ||
} | ||
|
||
return count; | ||
} | ||
|
||
static struct device_attribute attr_fwver = | ||
__ATTR(fw_version, 0440, gsc_show, NULL); | ||
static struct device_attribute attr_fwcrc = | ||
__ATTR(fw_crc, 0440, gsc_show, NULL); | ||
static struct device_attribute attr_pwrdown = | ||
__ATTR(powerdown, 0220, NULL, gsc_store); | ||
|
||
static struct attribute *gsc_attrs[] = { | ||
&attr_fwver.attr, | ||
&attr_fwcrc.attr, | ||
&attr_pwrdown.attr, | ||
NULL, | ||
}; | ||
|
||
static struct attribute_group attr_group = { | ||
.attrs = gsc_attrs, | ||
}; | ||
|
||
static const struct of_device_id gsc_of_match[] = { | ||
{ .compatible = "gw,gsc", }, | ||
{ } | ||
}; | ||
MODULE_DEVICE_TABLE(of, gsc_of_match); | ||
|
||
static struct regmap_bus gsc_regmap_bus = { | ||
.reg_read = gsc_read, | ||
.reg_write = gsc_write, | ||
}; | ||
|
||
static const struct regmap_config gsc_regmap_config = { | ||
.reg_bits = 8, | ||
.val_bits = 8, | ||
.cache_type = REGCACHE_NONE, | ||
.max_register = GSC_WP, | ||
}; | ||
|
||
static const struct regmap_irq gsc_irqs[] = { | ||
REGMAP_IRQ_REG(GSC_IRQ_PB, 0, BIT(GSC_IRQ_PB)), | ||
REGMAP_IRQ_REG(GSC_IRQ_KEY_ERASED, 0, BIT(GSC_IRQ_KEY_ERASED)), | ||
REGMAP_IRQ_REG(GSC_IRQ_EEPROM_WP, 0, BIT(GSC_IRQ_EEPROM_WP)), | ||
REGMAP_IRQ_REG(GSC_IRQ_RESV, 0, BIT(GSC_IRQ_RESV)), | ||
REGMAP_IRQ_REG(GSC_IRQ_GPIO, 0, BIT(GSC_IRQ_GPIO)), | ||
REGMAP_IRQ_REG(GSC_IRQ_TAMPER, 0, BIT(GSC_IRQ_TAMPER)), | ||
REGMAP_IRQ_REG(GSC_IRQ_WDT_TIMEOUT, 0, BIT(GSC_IRQ_WDT_TIMEOUT)), | ||
REGMAP_IRQ_REG(GSC_IRQ_SWITCH_HOLD, 0, BIT(GSC_IRQ_SWITCH_HOLD)), | ||
}; | ||
|
||
static const struct regmap_irq_chip gsc_irq_chip = { | ||
.name = "gateworks-gsc", | ||
.irqs = gsc_irqs, | ||
.num_irqs = ARRAY_SIZE(gsc_irqs), | ||
.num_regs = 1, | ||
.status_base = GSC_IRQ_STATUS, | ||
.mask_base = GSC_IRQ_ENABLE, | ||
.mask_invert = true, | ||
.ack_base = GSC_IRQ_STATUS, | ||
.ack_invert = true, | ||
}; | ||
|
||
static int gsc_probe(struct i2c_client *client) | ||
{ | ||
struct device *dev = &client->dev; | ||
struct gsc_dev *gsc; | ||
struct regmap_irq_chip_data *irq_data; | ||
int ret; | ||
unsigned int reg; | ||
|
||
gsc = devm_kzalloc(dev, sizeof(*gsc), GFP_KERNEL); | ||
if (!gsc) | ||
return -ENOMEM; | ||
|
||
gsc->dev = &client->dev; | ||
gsc->i2c = client; | ||
i2c_set_clientdata(client, gsc); | ||
|
||
gsc->regmap = devm_regmap_init(dev, &gsc_regmap_bus, client, | ||
&gsc_regmap_config); | ||
if (IS_ERR(gsc->regmap)) | ||
return PTR_ERR(gsc->regmap); | ||
|
||
if (regmap_read(gsc->regmap, GSC_FW_VER, ®)) | ||
return -EIO; | ||
gsc->fwver = reg; | ||
|
||
regmap_read(gsc->regmap, GSC_FW_CRC, ®); | ||
gsc->fwcrc = reg; | ||
regmap_read(gsc->regmap, GSC_FW_CRC + 1, ®); | ||
gsc->fwcrc |= reg << 8; | ||
|
||
gsc->i2c_hwmon = devm_i2c_new_dummy_device(dev, client->adapter, | ||
GSC_HWMON); | ||
if (IS_ERR(gsc->i2c_hwmon)) { | ||
dev_err(dev, "Failed to allocate I2C device for HWMON\n"); | ||
return PTR_ERR(gsc->i2c_hwmon); | ||
} | ||
|
||
ret = devm_regmap_add_irq_chip(dev, gsc->regmap, client->irq, | ||
IRQF_ONESHOT | IRQF_SHARED | | ||
IRQF_TRIGGER_FALLING, 0, | ||
&gsc_irq_chip, &irq_data); | ||
if (ret) | ||
return ret; | ||
|
||
dev_info(dev, "Gateworks System Controller v%d: fw 0x%04x\n", | ||
gsc->fwver, gsc->fwcrc); | ||
|
||
ret = sysfs_create_group(&dev->kobj, &attr_group); | ||
if (ret) | ||
dev_err(dev, "failed to create sysfs attrs\n"); | ||
|
||
ret = devm_of_platform_populate(dev); | ||
if (ret) { | ||
sysfs_remove_group(&dev->kobj, &attr_group); | ||
return ret; | ||
} | ||
|
||
return 0; | ||
} | ||
|
||
static int gsc_remove(struct i2c_client *client) | ||
{ | ||
sysfs_remove_group(&client->dev.kobj, &attr_group); | ||
|
||
return 0; | ||
} | ||
|
||
static struct i2c_driver gsc_driver = { | ||
.driver = { | ||
.name = "gateworks-gsc", | ||
.of_match_table = gsc_of_match, | ||
}, | ||
.probe_new = gsc_probe, | ||
.remove = gsc_remove, | ||
}; | ||
module_i2c_driver(gsc_driver); | ||
|
||
MODULE_AUTHOR("Tim Harvey <tharvey@gateworks.com>"); | ||
MODULE_DESCRIPTION("I2C Core interface for GSC"); | ||
MODULE_LICENSE("GPL v2"); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
/* SPDX-License-Identifier: GPL-2.0 | ||
* | ||
* Copyright (C) 2020 Gateworks Corporation | ||
*/ | ||
#ifndef __LINUX_MFD_GSC_H_ | ||
#define __LINUX_MFD_GSC_H_ | ||
|
||
#include <linux/regmap.h> | ||
|
||
/* Device Addresses */ | ||
#define GSC_MISC 0x20 | ||
#define GSC_UPDATE 0x21 | ||
#define GSC_GPIO 0x23 | ||
#define GSC_HWMON 0x29 | ||
#define GSC_EEPROM0 0x50 | ||
#define GSC_EEPROM1 0x51 | ||
#define GSC_EEPROM2 0x52 | ||
#define GSC_EEPROM3 0x53 | ||
#define GSC_RTC 0x68 | ||
|
||
/* Register offsets */ | ||
enum { | ||
GSC_CTRL_0 = 0x00, | ||
GSC_CTRL_1 = 0x01, | ||
GSC_TIME = 0x02, | ||
GSC_TIME_ADD = 0x06, | ||
GSC_IRQ_STATUS = 0x0A, | ||
GSC_IRQ_ENABLE = 0x0B, | ||
GSC_FW_CRC = 0x0C, | ||
GSC_FW_VER = 0x0E, | ||
GSC_WP = 0x0F, | ||
}; | ||
|
||
/* Bit definitions */ | ||
#define GSC_CTRL_0_PB_HARD_RESET 0 | ||
#define GSC_CTRL_0_PB_CLEAR_SECURE_KEY 1 | ||
#define GSC_CTRL_0_PB_SOFT_POWER_DOWN 2 | ||
#define GSC_CTRL_0_PB_BOOT_ALTERNATE 3 | ||
#define GSC_CTRL_0_PERFORM_CRC 4 | ||
#define GSC_CTRL_0_TAMPER_DETECT 5 | ||
#define GSC_CTRL_0_SWITCH_HOLD 6 | ||
|
||
#define GSC_CTRL_1_SLEEP_ENABLE 0 | ||
#define GSC_CTRL_1_SLEEP_ACTIVATE 1 | ||
#define GSC_CTRL_1_SLEEP_ADD 2 | ||
#define GSC_CTRL_1_SLEEP_NOWAKEPB 3 | ||
#define GSC_CTRL_1_WDT_TIME 4 | ||
#define GSC_CTRL_1_WDT_ENABLE 5 | ||
#define GSC_CTRL_1_SWITCH_BOOT_ENABLE 6 | ||
#define GSC_CTRL_1_SWITCH_BOOT_CLEAR 7 | ||
|
||
#define GSC_IRQ_PB 0 | ||
#define GSC_IRQ_KEY_ERASED 1 | ||
#define GSC_IRQ_EEPROM_WP 2 | ||
#define GSC_IRQ_RESV 3 | ||
#define GSC_IRQ_GPIO 4 | ||
#define GSC_IRQ_TAMPER 5 | ||
#define GSC_IRQ_WDT_TIMEOUT 6 | ||
#define GSC_IRQ_SWITCH_HOLD 7 | ||
|
||
int gsc_read(void *context, unsigned int reg, unsigned int *val); | ||
int gsc_write(void *context, unsigned int reg, unsigned int val); | ||
|
||
struct gsc_dev { | ||
struct device *dev; | ||
|
||
struct i2c_client *i2c; /* 0x20: interrupt controller, WDT */ | ||
struct i2c_client *i2c_hwmon; /* 0x29: hwmon, fan controller */ | ||
|
||
struct regmap *regmap; | ||
|
||
unsigned int fwver; | ||
unsigned short fwcrc; | ||
}; | ||
|
||
#endif /* __LINUX_MFD_GSC_H_ */ |