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