/*
* drivers/char/sunxi-owc/sunxi-owc.c
*
* Copyright (C) 2016-2020 Allwinner.
* lihuaxing <lihuaxing@allwinnertech.com>
*
* SUNXI OWC Controller Driver
*
* 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.
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/delay.h>
#include <linux/clk.h>
#include <linux/ioport.h>
#include <linux/interrupt.h>
#include <linux/err.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/major.h>
#include <linux/device.h>
#include <linux/uaccess.h>
#include <linux/gpio.h>
#include <linux/pinctrl/consumer.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <asm/irq.h>
#include <linux/io.h>
#include <linux/slab.h>
#include <linux/sched.h>
#include <asm/io.h>
#include "sunxi-owc.h"
#include "../w1.h"
#include "../w1_int.h"
/* ==================== For debug =============================== */
#define OWC_ENTER() pr_info("%s()%d - %s\n", __func__, __LINE__, "Enter ...")
#define OWC_EXIT() pr_info("%s()%d - %s\n", __func__, __LINE__, "Exit")
#define OWC_DBG(fmt, arg...) pr_debug("%s()%d - "fmt, __func__, __LINE__, ##arg)
#define OWC_INFO(fmt, arg...) pr_info("%s()%d - "fmt, __func__, __LINE__, ##arg)
#define OWC_WARN(fmt, arg...) pr_warn("%s()%d - "fmt, __func__, __LINE__, ##arg)
#define OWC_ERR(fmt, arg...) pr_err("%s()%d - "fmt, __func__, __LINE__, ##arg)
static struct sunxi_owc *powc;
/* clear control and status register */
static void owc_standard_setting(void __iomem *base_addr)
{
/* select standard mode */
u32 reg_val = readl(base_addr + OW_CTL);
reg_val &= ~(0x01 << 1);
writel(reg_val, base_addr + OW_CTL);
/* timing control register setting */
reg_val = 0;
reg_val |= (TSU & 0x3) << 29;
reg_val |= (TREC & 0xF) << 24;
reg_val |= (TRDV & 0x1F) << 18;
reg_val |= (TLOW0 & 0x7F) << 11;
reg_val |= (TLOW1 & 0xF) << 7;
reg_val |= (TSLOT & 0x7F) << 0;
writel(reg_val, base_addr + SM_WR_RD_TCTL);
/* reset presence timing control setting */
reg_val = 0;
reg_val |= (TPDL & 0xFF) << 24;
reg_val |= (TPDH & 0x3F) << 18;
reg_val |= (TRSTL & 0x1FF) << 9;
reg_val |= (TRSTH & 0x1FF) << 0;
writel(reg_val, base_addr + SM_RST_PRESENCE_TCTL);
}
static void owc_enable_irq(void __iomem *base_addr, u32 bitmap)
{
u32 reg_val = readl(base_addr + OW_INT_CTL);
reg_val |= bitmap;
writel(reg_val, base_addr + OW_INT_CTL);
}
#if 0
static void owc_disable_irq(void __iomem *base_addr, u32 bitmap)
{
u32 reg_val = readl(base_addr + OW_INT_CTL);
reg_val &= ~bitmap;
writel(reg_val, base_addr + OW_INT_CTL);
}
#endif
static void owc_config(void __iomem *base_addr)
{
u32 reg_val = readl(base_addr + OW_CTL);
reg_val |= 1 << 0;
reg_val |= 1 << 9;
writel(reg_val, base_addr + OW_CTL);
}
static void owc_fclk(void __iomem *base_addr, u32 fclk)
{
u32 reg_val = readl(base_addr + OW_FCLK);
reg_val &= ~OW_FCLK_MASK;
reg_val |= (fclk & 0x1F) << 16;
writel(reg_val, base_addr + OW_FCLK);
}
static void sunxi_owc_setup(struct sunxi_owc *powc)
{
/* set simple mode, enable inner pull-up */
owc_config(powc->reg_base);
/* set ow_fclk */
powc->func_clk = powc->clk_freq/1000000;
owc_fclk(powc->reg_base, powc->func_clk);
/* set standrad mode timing */
owc_standard_setting(powc->reg_base);
/* enable standard_mode irq */
owc_enable_irq(powc->reg_base, INTEN_DGCH|INTEN_ST_CRC_FINI
|INTEN_WR_CPL|INTEN_RD_CPL|INTEN_SP_TIME_OUT|INTEN_ST_INIT_SP_CPL);
}
#if 0
static void standard_crc_start(struct sunxi_owc *powc, bool crc_16, bool write)
{
u32 reg_val = readl(powc->reg_base + OW_SMSC);
if (crc_16)
reg_val |= (1 << 2);
else
reg_val &= ~(1 << 2);
if (write)
reg_val |= (1 << 1);
else
reg_val |= (1 << 0);
writel(reg_val, powc->reg_base + OW_SMSC);
}
static void standard_crc_stop(struct sunxi_owc *powc, bool write)
{
u32 reg_val = readl(powc->reg_base + OW_SMSC);
if (write)
reg_val &= ~(1 << 1);
else
reg_val &= ~(1 << 0);
writel(reg_val, powc->reg_base + OW_SMSC);
}
static int standard_crc_compare(struct sunxi_owc *powc)
{
u8 timeout = 0;
u32 reg_val = readl(powc->reg_base + OW_SMSC);
reg_val |= (1 << 3);
writel(reg_val, powc->reg_base + OW_SMSC);
timeout = wait_for_completion_timeout(&powc->done, usecs_to_jiffies(TIMEOUT));
if (timeout == 0) {
OWC_ERR("write timeout\n");
return -1;
} else {
reg_val = readl(powc->reg_base + OW_SMSC);
if (reg_val & (1 << 5))
return -1;
else
return 0;
}
}
#endif
static void standard_write(struct sunxi_owc *powc, u8 data)
{
u8 timeout = 0;
/* direction: 1-write, 0-read*/
u32 reg_val = readl(powc->reg_base + OW_CTL);
reg_val |= (1 << 2);
writel(reg_val, powc->reg_base + OW_CTL);
/* write data */
writel(data, powc->reg_base + OW_DATA);
/* start to send */
reg_val |= (1 << 4);
writel(reg_val, powc->reg_base + OW_CTL);
timeout = wait_for_completion_timeout(&powc->done, usecs_to_jiffies(TIMEOUT));
if (timeout == 0) {
OWC_ERR("write byte timeout\n");
}
}
static void write_bit(struct sunxi_owc *powc, u8 data)
{
u8 timeout = 0;
/* direction: 1-write, 0-read*/
u32 reg_val = readl(powc->reg_base + OW_CTL);
reg_val |= (1 << 2);
reg_val |= (1 << 5);
writel(reg_val, powc->reg_base + OW_CTL);
/* write data */
data &= 0x01;
writel(data, powc->reg_base + OW_DATA);
/* start to send */
reg_val |= (1 << 4);
writel(reg_val, powc->reg_base + OW_CTL);
timeout = wait_for_completion_timeout(&powc->done, usecs_to_jiffies(TIMEOUT));
if (timeout == 0) {
OWC_ERR("write bit timeout\n");
}
}
static int read_bit(struct sunxi_owc *powc)
{
u8 timeout = 0;
u8 data = 0;
/* direction: 1-write, 0-read*/
u32 reg_val = readl(powc->reg_base + OW_CTL);
reg_val &= ~(1 << 2);
reg_val |= (1 << 5);
writel(reg_val, powc->reg_base + OW_CTL);
/* start to send */
reg_val |= (1 << 4);
writel(reg_val, powc->reg_base + OW_CTL);
timeout = wait_for_completion_timeout(&powc->done, usecs_to_jiffies(TIMEOUT));
if (timeout == 0) {
OWC_ERR("read bit timeout\n");
return -ENOMEM;
} else {
reg_val = readl(powc->reg_base + OW_DATA) & 0xFF;
data = (u8)reg_val;
}
return data;
}
static int standard_read(struct sunxi_owc *powc)
{
u8 timeout = 0;
u8 data = 0;
/* direction: 1-write, 0-read*/
u32 reg_val = readl(powc->reg_base + OW_CTL);
reg_val &= ~(1 << 2);
writel(reg_val, powc->reg_base + OW_CTL);
/* start to send */
reg_val |= (1 << 4);
writel(reg_val, powc->reg_base + OW_CTL);
timeout = wait_for_completion_timeout(&powc->done, usecs_to_jiffies(TIMEOUT));
if (timeout == 0) {
OWC_ERR("read byte timeout\n");
return -ENOMEM;
} else {
reg_val = readl(powc->reg_base + OW_DATA) & 0xFF;
data = (u8)reg_val;
}
return data;
}
static int sunxi_owc_initialization(struct sunxi_owc *powc)
{
u32 reg_val = readl(powc->reg_base + OW_CTL);
u8 timeout = 0;
/* send initilization pulse */
reg_val |= (0x01 << 3);
writel(reg_val, powc->reg_base + OW_CTL);
reg_val |= (0x01 << 4);
writel(reg_val, powc->reg_base + OW_CTL);
timeout = wait_for_completion_timeout(&powc->done, usecs_to_jiffies(TIMEOUT));
if (powc->init_status) {
powc->init_status = 0;
}
if (timeout == 0) {
OWC_ERR("presence timeout\n");
return 1;
}
return 0;
}
/* get all interrupt status */
static u32 owc_get_interrupt_status(void __iomem *base_addr)
{
return readl(base_addr + OW_INT_STATUS);
}
/* clear interrupt status */
static void owc_clear_interrupt_status(void __iomem *base_addr, u32 bitmap)
{
writel(bitmap, base_addr + OW_INT_STATUS);
}
static irqreturn_t sunxi_owc_interrupt(int irqmun, void *dev_id)
{
struct sunxi_owc *powc = (struct sunxi_owc *)dev_id;
u32 irq_status = 0;
irq_status = owc_get_interrupt_status(powc->reg_base);
owc_clear_interrupt_status(powc->reg_base, irq_status);
/* standrad initilization Pulse finish */
if (irq_status & INT_ST_INIT_SP_CPL) {
powc->init_status = 1;
complete(&powc->done);
}
/* HDQ time out */
if (irq_status & INT_SP_TIME_OUT) {
}
/* standard/HDQ read completed */
if (irq_status & INT_OW_RD_CPL) {
complete(&powc->done);
}
/* standard/HDQ write completed */
if (irq_status & INT_OW_WR_CPL) {
complete(&powc->done);
}
/* standard CRC completed */
if (irq_status & INT_ST_CRC_FINI) {
complete(&powc->done);
}
/* deglitch detected */
if (irq_status & INT_DGCH_OCCUR) {
powc->presence_status = 1;
}
return IRQ_HANDLED;
}
static uint32_t sunxi_owc_clk_init(struct sunxi_owc *powc)
{
struct platform_device *pdev = powc->pdev;
struct device_node *node = pdev->dev.of_node;
if (NULL == pdev || !of_device_is_available(node)) {
OWC_ERR("platform_device invalid!\n");
return -EINVAL;
}
powc->pclk = of_clk_get(node, 0);
if (!powc->pclk || IS_ERR(powc->pclk)) {
OWC_ERR("err: try to get pclk clock fail!\n");
return -EINVAL;
}
powc->mclk = of_clk_get(node, 1);
if (!powc->mclk || IS_ERR(powc->mclk)) {
OWC_ERR("err: try to get mclk clock fail!\n");
return -EINVAL;
}
if (clk_set_parent(powc->mclk, powc->pclk)) {
OWC_ERR("set mclk parent to pclk fail!\n");
return -EINVAL;
}
if (of_property_read_u32(node, "clock-frequency", &powc->clk_freq)) {
OWC_INFO("get clock-frequency fail! use default 24Mhz\n");
powc->clk_freq = 24000000;
}
if (clk_set_rate(powc->mclk, powc->clk_freq)) {
OWC_ERR("set owc_clk freq failed!\n");
return -EINVAL;
}
if (clk_prepare_enable(powc->mclk)) {
OWC_ERR("try to enable owc_clk failed!\n");
return -EINVAL;
}
return 0;
}
static uint32_t sunxi_owc_clk_exit(struct sunxi_owc *powc)
{
if (NULL == powc->mclk || IS_ERR(powc->mclk)) {
OWC_ERR("owc_clk handle is invalid, just return!\n");
return -EINVAL;
} else {
clk_disable_unprepare(powc->mclk);
clk_put(powc->mclk);
powc->mclk = NULL;
}
if (NULL == powc->pclk || IS_ERR(powc->pclk)) {
OWC_ERR("owc pclk handle is invalid, just return!\n");
return -EINVAL;
} else {
clk_put(powc->pclk);
powc->pclk = NULL;
}
return 0;
}
static int owc_request_gpio(struct sunxi_owc *powc)
{
int ret = 0;
struct pinctrl_state *pctrl_state = NULL;
powc->pctrl = devm_pinctrl_get(&(powc->pdev->dev));
if (IS_ERR_OR_NULL(powc->pctrl)) {
OWC_ERR("request pinctrl handle fail!\n");
return -EINVAL;
}
pctrl_state = pinctrl_lookup_state(powc->pctrl, PINCTRL_STATE_DEFAULT);
if (IS_ERR(pctrl_state)) {
OWC_ERR("look_up_state fail!\n");
return -EINVAL;
}
ret = pinctrl_select_state(powc->pctrl, pctrl_state);
if (ret < 0) {
OWC_ERR("select_state fail!\n");
return -EINVAL;
}
return ret;
}
static void owc_release_gpio(struct sunxi_owc *powc)
{
if (!IS_ERR_OR_NULL(powc->pctrl))
devm_pinctrl_put(powc->pctrl);
powc->pctrl = NULL;
}
static u8 owc_read_bit(void *data)
{
struct sunxi_owc *powc = (struct sunxi_owc *)data;
return read_bit(powc);
}
static void owc_write_bit(void *data, u8 bit)
{
struct sunxi_owc *powc = (struct sunxi_owc *)data;
write_bit(powc, bit);
}
static u8 owc_read_byte(void *data)
{
struct sunxi_owc *powc = (struct sunxi_owc *)data;
return standard_read(powc);
}
static void owc_write_byte(void *data, u8 byte)
{
struct sunxi_owc *powc = (struct sunxi_owc *)data;
standard_write(powc, byte);
}
static u8 owc_reset_bus(void *data)
{
struct sunxi_owc *powc = (struct sunxi_owc *)data;
int ret;
ret = sunxi_owc_initialization(powc);
return ret;
}
static struct w1_bus_master sunxi_owc_master = {
.read_bit = owc_read_bit,
.write_bit = owc_write_bit,
.read_byte = owc_read_byte,
.write_byte = owc_write_byte,
.reset_bus = owc_reset_bus,
};
static int sunxi_owc_probe(struct platform_device *pdev)
{
struct device_node *node = pdev->dev.of_node;
struct resource *mem_res = NULL;
int ret = 0;
OWC_ENTER();
powc = kzalloc(sizeof(struct sunxi_owc), GFP_KERNEL);
if (!powc) {
OWC_ERR("kzalloc struct sunxi_owc fail!\n");
return -ENOMEM;
}
powc->pdev = pdev;
if (!of_device_is_available(node)) {
OWC_ERR("invalid node!\n");
ret = -EINVAL;
goto emloc;
}
if (sunxi_owc_clk_init(powc)) {
OWC_ERR("sunxi_owc_clk_init fail!\n");
ret = -EINVAL;
goto eclk;
}
powc->irq_num = platform_get_irq(pdev, 0);
if (powc->irq_num < 0) {
OWC_ERR("get irq number fail!\n");
ret = -EINVAL;
goto eclk;
}
mem_res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (mem_res == NULL) {
OWC_ERR("failed to get MEM res\n");
ret = -ENXIO;
goto eclk;
}
if (!request_mem_region(mem_res->start,
resource_size(mem_res), mem_res->name)) {
OWC_ERR("failed to request mem region\n");
ret = -EINVAL;
goto eclk;
}
powc->reg_base = ioremap(mem_res->start, resource_size(mem_res));
if (!powc->reg_base) {
OWC_ERR("failed to io remap\n");
ret = -EIO;
goto eiomem;
}
powc->mem_res = mem_res;
if (request_irq(powc->irq_num, sunxi_owc_interrupt,
IRQF_TRIGGER_NONE, "owc", powc)) {
OWC_ERR("request irq fail!\n");
ret = -EINVAL;
goto eiomap;
}
if (owc_request_gpio(powc)) {
OWC_ERR("failed to request gpio\n");
ret = -EINVAL;
goto eirq;
}
spin_lock_init(&powc->owc_lock);
init_completion(&powc->done);
sunxi_owc_setup(powc);
sunxi_owc_master.data = (void *)powc;
ret = w1_add_master_device(&sunxi_owc_master);
platform_set_drvdata(pdev, powc);
return 0;
eirq:
free_irq(powc->irq_num, powc);
eiomap:
iounmap(powc->reg_base);
eiomem:
release_mem_region(mem_res->start, resource_size(mem_res));
eclk:
sunxi_owc_clk_exit(powc);
emloc:
kfree(powc);
return ret;
}
static int sunxi_owc_remove(struct platform_device *pdev)
{
struct sunxi_owc *powc = platform_get_drvdata(pdev);
w1_remove_master_device(&sunxi_owc_master);
free_irq(powc->irq_num, powc);
iounmap(powc->reg_base);
release_mem_region(powc->mem_res->start,
resource_size(powc->mem_res));
owc_release_gpio(powc);
sunxi_owc_clk_exit(powc);
OWC_EXIT();
return 0;
}
#ifdef CONFIG_PM
static int sunxi_owc_suspend(struct device *dev)
{
struct platform_device *pdev = to_platform_device(dev);
struct sunxi_owc *powc = platform_get_drvdata(pdev);
struct pinctrl_state *pctrl_state = NULL;
powc->suspended = true;
if (sunxi_owc_clk_exit(powc)) {
OWC_ERR("OWC suspend failed !\n");
powc->suspended = false;
return -1;
}
if (!IS_ERR_OR_NULL(powc->pctrl)) {
pctrl_state = pinctrl_lookup_state(powc->pctrl,
PINCTRL_STATE_SLEEP);
if (IS_ERR(pctrl_state)) {
OWC_ERR("OWC pinctrl lookup sleep fail\n");
return -1;
}
if (pinctrl_select_state(powc->pctrl, pctrl_state) < 0) {
OWC_ERR("OWC pinctrl select sleep fail\n");
return -1;
}
}
disable_irq_nosync(powc->irq_num);
OWC_DBG("OWC suspend okay\n");
return 0;
}
static int sunxi_owc_resume(struct device *dev)
{
struct platform_device *pdev = to_platform_device(dev);
struct sunxi_owc *powc = platform_get_drvdata(pdev);
struct pinctrl_state *pctrl_state = NULL;
powc->suspended = false;
if (sunxi_owc_clk_init(powc)) {
OWC_ERR("OWC resume failed !\n");
return -1;
}
if (!IS_ERR_OR_NULL(powc->pctrl)) {
pctrl_state = pinctrl_lookup_state(powc->pctrl,
PINCTRL_STATE_DEFAULT);
if (IS_ERR(pctrl_state)) {
OWC_ERR("OWC pinctrl lookup default fail\n");
return -1;
}
if (pinctrl_select_state(powc->pctrl, pctrl_state) < 0) {
OWC_ERR("OWC pinctrl select default fail\n");
return -1;
}
}
enable_irq(powc->irq_num);
OWC_DBG("OWC resume okay\n");
return 0;
}
static const struct dev_pm_ops sunxi_owc_dev_pm_ops = {
.suspend = sunxi_owc_suspend,
.resume = sunxi_owc_resume,
};
#define SUNXI_OWC_DEV_PM_OPS (&sunxi_owc_dev_pm_ops)
#else
#define SUNXI_OWC_DEV_PM_OPS NULL
#endif
static const struct of_device_id sunxi_owc_match[] = {
{.compatible = "allwinner,sunxi-owc",},
{},
};
MODULE_DEVICE_TABLE(of, sunxi_owc_match);
static struct platform_driver owc_platform_driver = {
.probe = sunxi_owc_probe,
.remove = sunxi_owc_remove,
.driver = {
.name = OWC_MODULE_NAME,
.owner = THIS_MODULE,
.pm = SUNXI_OWC_DEV_PM_OPS,
.of_match_table = sunxi_owc_match,
},
};
static int __init sunxi_owc_init(void)
{
return platform_driver_register(&owc_platform_driver);
}
static void __exit sunxi_owc_exit(void)
{
platform_driver_unregister(&owc_platform_driver);
}
module_init(sunxi_owc_init);
module_exit(sunxi_owc_exit);
MODULE_DESCRIPTION("One Wire Driver");
MODULE_AUTHOR("lihuaxing");
MODULE_LICENSE("GPL");