blob: 31c20f0b49a96262466832aedbfa2d5ec77c5829 [file] [log] [blame]
/*
* drivers/amlogic/i2c/aml_slave.c
*
* Copyright (C) 2017 Amlogic, Inc. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
*/
#include <linux/module.h>
#include "aml_slave.h"
#include <linux/slab.h>
#include <linux/of_irq.h>
#include <linux/interrupt.h>
#include <linux/amlogic/iomap.h>
/*uboot i2c_auto_test: open/disable*/
static char i2c_auto_test_mode[10] = "disable";
static char i2c_auto_test_flag;
void i2c_slave_dts_parse(struct platform_device *pdev,
struct aml_i2c_slave *slave)
{
struct pinctrl *p;
p = devm_pinctrl_get_select_default(&pdev->dev);
if (IS_ERR(p))
pr_info("pinmux init fail, %ld\n", PTR_ERR(p));
slave->irq = platform_get_irq(pdev, 0);
pr_info("irq = %d\n", slave->irq);
}
void i2c_slave_init(struct platform_device *pdev,
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 = container_of(class, struct aml_i2c_slave, cls);
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 = container_of(class,
struct aml_i2c_slave, cls);
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_class_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
};
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);
if (!slave)
return -ENOMEM;
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);
pr_info("res:%pa res_start:%pa\n", &res->start, res_start);
i2c_slave_dts_parse(pdev, slave);
slave->lock = kzalloc(sizeof(struct mutex), GFP_KERNEL);
mutex_init(slave->lock);
i2c_slave_init(pdev, 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.name = kzalloc(8, GFP_KERNEL);
sprintf((char *)slave->cls.name, "i2c_slave");
slave->cls.class_attrs = slave_class_attrs;
ret = class_register(&slave->cls);
#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);
return 0;
}
static int __init i2c_auto_test_setup(char *s)
{
if (s != NULL)
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(pdev, 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 = "aml_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);