blob: 3c6543a1688c038e1bcba86b0ad8a6dd75f4ce9e [file]
// SPDX-License-Identifier: GPL-2.0-only
/*
* X-Powers AC200 Core driver
*
* Copyright (C) 2019 Jernej Skrabec <jernej.skrabec@gmail.com>
* Copyright (C) 2026 James Hilliard <james.hilliard1@gmail.com>
*/
#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/i2c.h>
#include <linux/irq.h>
#include <linux/mfd/core.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/regmap.h>
#define AC200_SYS_CONTROL_REG 0x0002
#define AC200_SYS_CONTROL_CHIP_RESET_DEASSERT BIT(0)
#define AC200_SYS_IRQ_ENABLE_REG 0x0004
#define AC200_SYS_IRQ_INTB_ENABLE BIT(15)
#define AC200_SYS_IRQ_INTB_ACTIVE_HIGH BIT(14)
#define AC200_SYS_IRQ_RTC BIT(12)
#define AC200_SYS_IRQ_EPHY BIT(8)
#define AC200_SYS_IRQ_TVE BIT(4)
/* Interface register accessible from every register page. */
#define AC200_TWI_REG_ADDR_H 0x00fe
#define AC200_MAX_REG 0xa1f2
static const struct regmap_range_cfg ac200_range_cfg[] = {
{
.range_max = AC200_MAX_REG,
.selector_reg = AC200_TWI_REG_ADDR_H,
.selector_mask = 0xff,
.window_len = 256,
},
};
/*
* Each AC200 sub-block can reset independently, invalidating its register
* contents without regmap's knowledge. Cache only the common page selector;
* this avoids a selector read-modify-write for every access on the same page
* without ever returning stale functional-register values.
*/
static bool ac200_volatile_reg(struct device *dev, unsigned int reg)
{
return reg != AC200_TWI_REG_ADDR_H;
}
static const struct regmap_config ac200_regmap_config = {
.name = "ac200",
.reg_bits = 8,
.reg_stride = 2,
.val_bits = 16,
.ranges = ac200_range_cfg,
.num_ranges = ARRAY_SIZE(ac200_range_cfg),
.max_register = AC200_MAX_REG,
.volatile_reg = ac200_volatile_reg,
.cache_type = REGCACHE_MAPLE,
};
static const char * const ac200_codec_supplies[] = {
"ac-ldoin",
};
static const char * const ac200_tve_supplies[] = {
"tv-vcc",
};
static const struct resource ac200_tve_resources[] = {
DEFINE_RES_IRQ_NAMED(0, "intb"),
};
#define AC200_CELL(_name, _supplies, _resources) \
{ \
.name = (_name), \
.parent_supplies = (_supplies), \
.num_parent_supplies = ARRAY_SIZE(_supplies), \
.resources = (_resources), \
.num_resources = MFD_RES_SIZE(_resources), \
}
static const struct mfd_cell ac200_cells[] = {
AC200_CELL("ac200-codec", ac200_codec_supplies, NULL),
AC200_CELL("ac200-tve", ac200_tve_supplies, ac200_tve_resources),
};
static const struct mfd_cell ac200_noirq_cells[] = {
AC200_CELL("ac200-codec", ac200_codec_supplies, NULL),
AC200_CELL("ac200-tve", ac200_tve_supplies, NULL),
};
static void ac200_disable_intb(void *data)
{
struct regmap *regmap = data;
int ret;
ret = regmap_clear_bits(regmap, AC200_SYS_IRQ_ENABLE_REG, AC200_SYS_IRQ_INTB_ENABLE);
if (ret)
dev_err(regmap_get_device(regmap), "failed to disable INTB: %d\n", ret);
}
static int ac200_init_irq(struct device *dev, struct regmap *regmap, int irq)
{
unsigned int trigger;
u16 value = 0;
int ret;
trigger = irq_get_trigger_type(irq);
switch (trigger) {
case IRQ_TYPE_LEVEL_HIGH:
value |= AC200_SYS_IRQ_INTB_ACTIVE_HIGH;
break;
case IRQ_TYPE_NONE:
case IRQ_TYPE_LEVEL_LOW:
break;
default:
return dev_err_probe(dev, -EINVAL,
"INTB is level triggered, not type %u\n", trigger);
}
/* Mask every source before enabling the shared output. */
ret = regmap_update_bits(regmap, AC200_SYS_IRQ_ENABLE_REG,
AC200_SYS_IRQ_INTB_ENABLE | AC200_SYS_IRQ_INTB_ACTIVE_HIGH |
AC200_SYS_IRQ_RTC | AC200_SYS_IRQ_EPHY | AC200_SYS_IRQ_TVE, value);
if (ret)
return ret;
/* Children are removed before INTB is disabled and the regmap released. */
ret = devm_add_action_or_reset(dev, ac200_disable_intb, regmap);
if (ret)
return ret;
if (trigger == IRQ_TYPE_NONE) {
ret = irq_set_irq_type(irq, IRQ_TYPE_LEVEL_LOW);
if (ret)
return dev_err_probe(dev, ret, "failed to set INTB trigger\n");
}
/*
* Function drivers request INTB with IRQF_SHARED | IRQF_ONESHOT before
* enabling their own source. They check and clear their own status and
* mask their source before freeing the IRQ. No IRQ domain is needed.
*/
return regmap_set_bits(regmap, AC200_SYS_IRQ_ENABLE_REG, AC200_SYS_IRQ_INTB_ENABLE);
}
static int ac200_probe(struct i2c_client *client)
{
const struct mfd_cell *cells = ac200_noirq_cells;
unsigned int num_cells = ARRAY_SIZE(ac200_noirq_cells);
struct device *dev = &client->dev;
struct regmap *regmap;
struct clk *clk;
int ret;
clk = devm_clk_get_enabled(dev, NULL);
if (IS_ERR(clk))
return dev_err_probe(dev, PTR_ERR(clk), "failed to enable input clock\n");
ret = devm_clk_rate_exclusive_get(dev, clk);
if (ret)
return dev_err_probe(dev, ret, "failed to lock clock rate\n");
/*
* No minimum delay is documented. Retain the vendor resume path's 40 ms
* wait after enabling the input clock and before register access.
*/
msleep(40);
regmap = devm_regmap_init_i2c(client, &ac200_regmap_config);
if (IS_ERR(regmap))
return dev_err_probe(dev, PTR_ERR(regmap), "failed to initialize regmap\n");
ret = regmap_set_bits(regmap, AC200_SYS_CONTROL_REG, AC200_SYS_CONTROL_CHIP_RESET_DEASSERT);
if (ret)
return ret;
if (client->irq > 0) {
ret = ac200_init_irq(dev, regmap, client->irq);
if (ret)
return ret;
cells = ac200_cells;
num_cells = ARRAY_SIZE(ac200_cells);
}
/* Function drivers read their firmware properties from the parent. */
ret = devm_mfd_add_devices(dev, PLATFORM_DEVID_AUTO, cells, num_cells,
NULL, client->irq, NULL);
if (ret)
return dev_err_probe(dev, ret, "failed to add function devices\n");
return 0;
}
static const struct of_device_id ac200_of_match[] = {
{ .compatible = "x-powers,ac200" },
{ }
};
MODULE_DEVICE_TABLE(of, ac200_of_match);
static const struct i2c_device_id ac200_i2c_ids[] = {
{ .name = "ac200" },
{ }
};
MODULE_DEVICE_TABLE(i2c, ac200_i2c_ids);
static struct i2c_driver ac200_driver = {
.driver = {
.name = "ac200",
.of_match_table = ac200_of_match,
},
.probe = ac200_probe,
.id_table = ac200_i2c_ids,
};
module_i2c_driver(ac200_driver);
MODULE_AUTHOR("Jernej Skrabec <jernej.skrabec@gmail.com>");
MODULE_AUTHOR("James Hilliard <james.hilliard1@gmail.com>");
MODULE_DESCRIPTION("X-Powers AC200 Core driver");
MODULE_LICENSE("GPL");