summaryrefslogtreecommitdiffstats
path: root/drivers/mfd/da9063.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/mfd/da9063.c')
-rw-r--r--drivers/mfd/da9063.c159
1 files changed, 148 insertions, 11 deletions
diff --git a/drivers/mfd/da9063.c b/drivers/mfd/da9063.c
index ac303611c..0c27bf495 100644
--- a/drivers/mfd/da9063.c
+++ b/drivers/mfd/da9063.c
@@ -27,14 +27,26 @@ struct da9063 {
struct restart_handler restart;
struct watchdog wd;
struct i2c_client *client;
+ /* dummy client for accessing bank #1 */
+ struct i2c_client *client1;
struct device_d *dev;
+ unsigned int timeout;
};
+/* forbidden/impossible value; timeout will be set to this value initially to
+ * detect ping vs. set_timeout() operations. */
+#define DA9063_INITIAL_TIMEOUT (~0u)
+
/* System Control and Event Registers */
#define DA9063_REG_FAULT_LOG 0x05
#define DA9063_REG_CONTROL_D 0x11
#define DA9063_REG_CONTROL_F 0x13
+/* bank1: control register I */
+#define DA9063_REG1_CONFIG_I 0x10e
+
+#define DA9062AA_DEVICE_ID 0x181
+
/* DA9063_REG_FAULT_LOG (addr=0x05) */
#define DA9063_TWD_ERROR 0x01
#define DA9063_POR 0x02
@@ -44,21 +56,89 @@ struct da9063 {
#define DA9063_TWDSCALE_MASK 0x07
/* DA9063_REG_CONTROL_F (addr=0x13) */
+#define DA9063_WATCHDOG 0x01
#define DA9063_SHUTDOWN 0x02
+/* DA9063_REG_CONTROL_I (addr=0x10e) */
+#define DA9062_WATCHDOG_SD BIT(3)
+
+struct da906x_device_data {
+ int (*init)(struct da9063 *priv);
+};
+
+static int da906x_reg_update(struct da9063 *priv, unsigned int reg,
+ uint8_t mask, uint8_t val)
+{
+ struct i2c_client *client;
+ uint8_t tmp;
+ int ret;
+
+ if (reg < 0x100)
+ client = priv->client;
+ else if (reg < 0x200)
+ client = priv->client1;
+ else
+ /* this should/can not happen because function is usually
+ * called with a static register number; warn about it
+ * below */
+ client = NULL;
+
+ if (WARN_ON(!client))
+ return -EINVAL;
+
+ ret = i2c_read_reg(client, reg & 0xffu, &tmp, 1);
+ if (ret < 0) {
+ dev_warn(priv->dev, "failed to read reg %02x\n", reg);
+ return ret;
+ }
+
+ tmp &= ~mask;
+ tmp |= val;
+
+ ret = i2c_write_reg(client, reg & 0xffu, &tmp, 1);
+ if (ret < 0) {
+ dev_warn(priv->dev, "failed to write %02x into reg %02x\n",
+ tmp, reg);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int da9063_watchdog_ping(struct da9063 *priv)
+{
+ int ret;
+ u8 val;
+
+ dev_dbg(priv->dev, "ping\n");
+
+ /* reset watchdog timer; register is self clearing */
+ val = DA9063_WATCHDOG;
+ ret = i2c_write_reg(priv->client, DA9063_REG_CONTROL_F, &val, 1);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
static int da9063_watchdog_set_timeout(struct watchdog *wd, unsigned timeout)
{
struct da9063 *priv = container_of(wd, struct da9063, wd);
struct device_d *dev = priv->dev;
unsigned int scale = 0;
int ret;
- u8 val;
if (timeout > 131)
return -EINVAL;
+ timeout *= 1000; /* convert to ms */
+
+ if (timeout == priv->timeout)
+ /* set_timeout called with previous parameter; just ping the
+ * watchdog */
+ goto out;
+
if (timeout) {
- timeout *= 1000; /* convert to ms */
scale = 0;
while (timeout > (2048 << scale) && scale <= 6)
scale++;
@@ -67,18 +147,15 @@ static int da9063_watchdog_set_timeout(struct watchdog *wd, unsigned timeout)
scale++; /* scale 0 disables the WD */
}
- ret = i2c_read_reg(priv->client, DA9063_REG_CONTROL_D, &val, 1);
+ ret = da906x_reg_update(priv, DA9063_REG_CONTROL_D,
+ DA9063_TWDSCALE_MASK, scale);
if (ret < 0)
return ret;
- val &= ~DA9063_TWDSCALE_MASK;
- val |= scale;
-
- ret = i2c_write_reg(priv->client, DA9063_REG_CONTROL_D, &val, 1);
- if (ret < 0)
- return ret;
+ priv->timeout = timeout;
- return 0;
+out:
+ return da9063_watchdog_ping(priv);
}
static void da9063_detect_reset_source(struct da9063 *priv)
@@ -118,18 +195,65 @@ static void da9063_restart(struct restart_handler *rst)
i2c_write_reg(priv->client, DA9063_REG_CONTROL_F, &val, 1);
}
+static int da9062_device_init(struct da9063 *priv)
+{
+ int ret;
+ uint8_t id[4];
+
+ priv->client1 = i2c_new_dummy(priv->client->adapter,
+ priv->client->addr + 1);
+ if (!priv) {
+ dev_warn(priv->dev, "failed to create bank 1 device\n");
+ /* TODO: return -EINVAL; i2c api does not return more
+ * details */
+ return -EINVAL;
+ }
+
+ ret = i2c_read_reg(priv->client1, DA9062AA_DEVICE_ID & 0xffu,
+ id, sizeof id);
+ if (ret < 0) {
+ dev_warn(priv->dev, "failed to read ID: %d\n", ret);
+ return ret;
+ }
+
+ dev_info(priv->dev, "da9062 with id %02x.%02x.%02x.%02x detected\n",
+ id[0], id[1], id[2], id[3]);
+
+ /* clear CONFIG_I[WATCHDOG_SD] */
+ ret = da906x_reg_update(priv, DA9063_REG1_CONFIG_I,
+ DA9062_WATCHDOG_SD, DA9062_WATCHDOG_SD);
+
+ return ret;
+}
+
+static struct da906x_device_data const da9062_device_data = {
+ .init = da9062_device_init,
+};
+
static int da9063_probe(struct device_d *dev)
{
struct da9063 *priv = NULL;
+ struct da906x_device_data const *dev_data;
+ void const *dev_data_tmp;
int ret;
+ ret = dev_get_drvdata(dev, &dev_data_tmp);
+ dev_data = ret < 0 ? NULL : dev_data_tmp;
+
priv = xzalloc(sizeof(struct da9063));
priv->wd.priority = of_get_watchdog_priority(dev->device_node);
priv->wd.set_timeout = da9063_watchdog_set_timeout;
priv->wd.hwdev = dev;
+ priv->timeout = DA9063_INITIAL_TIMEOUT;
priv->client = to_i2c_client(dev);
priv->dev = dev;
+ if (dev_data && dev_data->init) {
+ ret = dev_data->init(priv);
+ if (ret < 0)
+ goto on_error;
+ }
+
ret = watchdog_register(&priv->wd);
if (ret)
goto on_error;
@@ -151,14 +275,27 @@ on_error:
}
static struct platform_device_id da9063_id[] = {
- { "da9063", },
+ { "da9063", (uintptr_t)(NULL) },
+ { "da9062", (uintptr_t)(&da9062_device_data) },
{ }
};
+static struct of_device_id const da906x_dt_ids[] = {
+ {
+ .compatible = "dlg,da9063",
+ .data = NULL,
+ }, {
+ .compatible = "dlg,da9062",
+ .data = &da9062_device_data,
+ }, {
+ }
+};
+
static struct driver_d da9063_driver = {
.name = "da9063",
.probe = da9063_probe,
.id_table = da9063_id,
+ .of_compatible = DRV_OF_COMPAT(da906x_dt_ids),
};
static int da9063_init(void)