blob: a8c104f0494fe936ec157fd9ce56f7f8647039ab [file] [log] [blame]
// SPDX-License-Identifier: (GPL-2.0+ OR MIT)
/*
* Copyright (c) 2019 Amlogic, Inc. All rights reserved.
*/
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/of_irq.h>
#include <linux/interrupt.h>
#include <linux/platform_device.h>
#include <linux/amlogic/iomap.h>
#include "meson_i2c_slave.h"
/*uboot i2c_auto_test: open/disable*/
static char i2c_auto_test_mode[10] = "disable";
static char i2c_auto_test_flag;
struct aml_i2c_slave *g_i2c;
static void i2c_slave_init(struct aml_i2c_slave *slave)
{
slave->slave_regs->s_reg_ctrl = 0x0;
slave->slave_regs->s_reg_ctrl |= (1 << 7);/*machine state en*/
slave->slave_regs->s_reg_ctrl |= (1 << 25);/*IRQ_EN*/
slave->slave_regs->s_reg_ctrl |= (1 << 24);/*ack en*/
slave->slave_regs->s_reg_ctrl |= (1 << 28);/*send en*/
slave->slave_regs->s_reg_ctrl |= (1 << 27);/*recv en*/
slave->slave_regs->s_reg_ctrl |= (0x40 << 16); /*slave addr*/
slave->slave_regs->s_reg_ctrl |= (0x0 << 8); /*hold time*/
slave->slave_regs->s_reg_ctrl |= (0x0 << 0); /*sampling rate*/
}
static ssize_t show_i2c_slave(struct class *class,
struct class_attribute *attr, char *buf)
{
int ret;
struct aml_i2c_slave *i2c;
i2c = g_i2c;
if (!strcmp(attr->attr.name, "addr")) {
ret = sprintf(buf, "addr=0x%x\n",
(i2c->slave_regs->s_reg_ctrl >> 16 & (0xff)) >> 1);
} else if (!strcmp(attr->attr.name, "control_reg")) {
ret = sprintf(buf, "control_reg=0x%x\n",
i2c->slave_regs->s_reg_ctrl & (0xffffffff));
} else if (!strcmp(attr->attr.name, "data")) {
ret = sprintf(buf, "data=0x%x\n",
i2c->slave_regs->s_rev_reg & (0xffffffff));
} else {
pr_info("error attribute access\n");
ret = 0;
}
return ret;
}
static ssize_t store_i2c_slave(struct class *class,
struct class_attribute *attr, const char *buf, size_t count)
{
struct aml_i2c_slave *i2c = g_i2c;
int ret;
unsigned long val = 0;
ret = kstrtoul(buf, 0, &val);
if (ret)
return ret;
if (!strcmp(attr->attr.name, "addr")) {
mutex_lock(i2c->lock);
i2c->slave_regs->s_reg_ctrl &= ~(0xff << 16);
i2c->slave_regs->s_reg_ctrl |= (val & 0xff) << 16;
mutex_unlock(i2c->lock);
} else if (!strcmp(attr->attr.name, "control_reg")) {
mutex_lock(i2c->lock);
i2c->slave_regs->s_reg_ctrl = (val & 0xffffffff);
mutex_unlock(i2c->lock);
} else if (!strcmp(attr->attr.name, "data")) {
mutex_lock(i2c->lock);
i2c->slave_regs->s_send_reg = val & 0xffffffff;
mutex_unlock(i2c->lock);
}
return count;
}
static struct class_attribute slave_attrs[] = {
__ATTR(addr, 0644, show_i2c_slave, store_i2c_slave),
__ATTR(control_reg, 0644, show_i2c_slave,
store_i2c_slave),
__ATTR(data, 0644, show_i2c_slave, store_i2c_slave),
__ATTR_NULL
};
static struct attribute *slave_class_attrs[] = {
&slave_attrs[0].attr,
&slave_attrs[1].attr,
&slave_attrs[2].attr,
NULL
};
ATTRIBUTE_GROUPS(slave_class);
static struct class slave_class = {
.name = "i2c_slave_class",
.owner = THIS_MODULE,
.class_groups = slave_class_groups,
};
int i2c_slave_read_data(struct aml_i2c_slave *slave,
struct aml_i2c_slave_reg_ctrl *slave_ctrl)
{
unsigned int rev_data;
rev_data = (slave->slave_regs->s_rev_reg);
pr_info("slave receive data:0x%8x\n", rev_data);
if (i2c_auto_test_flag) {
if (rev_data == 0x998855aa)
slave->slave_regs->s_send_reg = rev_data & 0xffffffff;
}
slave->slave_regs->s_reg_ctrl |= (1 << 27);
return 0;
}
#ifdef CONFIG_I2C_SLAVE_TIMER
static void i2c_slave_check_status(unsigned long lparam)
{
struct aml_i2c_slave *slave = (struct aml_i2c_slave *)lparam;
struct aml_i2c_slave_reg_ctrl *slave_ctrl;
slave_ctrl = (struct aml_i2c_slave_reg_ctrl *)&
(slave->slave_regs->s_reg_ctrl);
if (((slave->slave_regs->s_reg_ctrl >> 27) & (0x1)) == 0x1)
mod_timer(&slave->timer, jiffies +
msecs_to_jiffies(slave->time_out));
else if (!i2c_slave_read_data(slave, slave_ctrl))
return;
}
#endif
static irqreturn_t i2c_slave_xfer_isr(int irq, void *dev_id)
{
struct aml_i2c_slave *slave;
struct aml_i2c_slave_reg_ctrl *slave_ctrl;
slave = (struct aml_i2c_slave *)dev_id;
slave_ctrl = (struct aml_i2c_slave_reg_ctrl *)&
(slave->slave_regs->s_reg_ctrl);
if (((slave->slave_regs->s_reg_ctrl >> 27) & (0x1)) == 0x0)
if (!i2c_slave_read_data(slave, slave_ctrl))
return IRQ_HANDLED;
if (((slave->slave_regs->s_reg_ctrl >> 28) & (0x1)) == 0x0)
pr_info("\n");
#ifdef CONFIG_I2C_SLAVE_TIMER
slave->timer.data = (unsigned long)slave;
mod_timer(&slave->timer, jiffies +
msecs_to_jiffies(slave->time_out));
#endif
return IRQ_HANDLED;
}
static int i2c_slave_probe(struct platform_device *pdev)
{
struct resource *res;
resource_size_t *res_start;
int ret;
struct aml_i2c_slave *slave;
i2c_auto_test_flag = 0;
if (!strcmp(i2c_auto_test_mode, "open"))
i2c_auto_test_flag = 1;
slave = devm_kzalloc(&pdev->dev, sizeof(struct aml_i2c_slave),
GFP_KERNEL);
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
res_start = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(res_start))
return PTR_ERR(res_start);
slave->slave_regs = (struct aml_i2c_reg_slave __iomem *)(res_start);
slave->irq = platform_get_irq(pdev, 0);
slave->lock = kzalloc(sizeof(*slave->lock), GFP_KERNEL);
mutex_init(slave->lock);
i2c_slave_init(slave);
ret = request_irq(slave->irq, i2c_slave_xfer_isr,
IRQF_SHARED, "aml_slave", (void *)slave);
if (ret < 0)
dev_info(&pdev->dev, "slave request irq(%d) failed!\n",
slave->irq);
else
dev_info(&pdev->dev, "slave work in interrupt (irq=%d)\n",
slave->irq);
slave->cls = &slave_class;
ret = class_register(&slave_class);
#ifdef CONFIG_I2C_SLAVE_TIMER
slave->time_out = 20;
setup_timer(&slave->timer,
i2c_slave_check_status, (unsigned long)slave);
#endif
platform_set_drvdata(pdev, slave);
g_i2c = slave;
return 0;
}
static int __init i2c_auto_test_setup(char *s)
{
if (!s)
sprintf(i2c_auto_test_mode, "%s", s);
return 0;
}
__setup("i2c_auto_test=", i2c_auto_test_setup);
static int i2c_slave_remove(struct platform_device *pdev)
{
struct aml_i2c_slave *slave;
slave = platform_get_drvdata(pdev);
class_destroy(slave->cls);
mutex_destroy(slave->lock);
free_irq(slave->irq, slave);
return 0;
}
static int i2c_slave_suspend(struct device *dev)
{
struct platform_device *pdev = to_platform_device(dev);
struct aml_i2c_slave *slave;
slave = platform_get_drvdata(pdev);
mutex_lock(slave->lock);
disable_irq(slave->irq);
mutex_unlock(slave->lock);
pr_info("%s: disable #%d irq\n", __func__, slave->irq);
return 0;
}
static int i2c_slave_resume(struct device *dev)
{
struct platform_device *pdev = to_platform_device(dev);
struct aml_i2c_slave *slave;
slave = platform_get_drvdata(pdev);
pr_info("%s\n", __func__);
mutex_lock(slave->lock);
i2c_slave_init(slave);
enable_irq(slave->irq);
mutex_unlock(slave->lock);
pr_info("%s: enable #%d irq\n", __func__, slave->irq);
return 0;
}
#ifdef CONFIG_OF
static const struct of_device_id i2c_slave_matches[] = {
{ .compatible = "amlogic, meson-i2c-slave", },
{},
};
#endif
static const struct dev_pm_ops i2c_slave_pm_ops = {
.suspend_noirq = i2c_slave_suspend,
.resume_noirq = i2c_slave_resume,
};
static struct platform_driver i2c_slave_driver = {
.driver = {
.name = "meson_i2c_slave",
.owner = THIS_MODULE,
.of_match_table = of_match_ptr(i2c_slave_matches),
.pm = &i2c_slave_pm_ops,
},
.probe = i2c_slave_probe,
.remove = i2c_slave_remove,
};
module_platform_driver(i2c_slave_driver);