firmware/br-ext-chip-allwinner/board/v83x/kernel/patches/00000-drivers_w1_masters_su...

737 lines
17 KiB
Diff

diff -drupN a/drivers/w1/masters/sunxi-owc.c b/drivers/w1/masters/sunxi-owc.c
--- a/drivers/w1/masters/sunxi-owc.c 1970-01-01 03:00:00.000000000 +0300
+++ b/drivers/w1/masters/sunxi-owc.c 2022-06-12 05:28:14.000000000 +0300
@@ -0,0 +1,732 @@
+/*
+ * 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");