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 + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#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");