linux owc 驱动

2 篇文章 0 订阅

/*
 * 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");

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值