diff --git a/drivers/w1/masters/Kconfig b/drivers/w1/masters/Kconfig index 24b9a8e05f64..8c2f6e045fe6 100644 --- a/drivers/w1/masters/Kconfig +++ b/drivers/w1/masters/Kconfig @@ -74,5 +74,15 @@ config W1_MASTER_SGI This support is also available as a module. If so, the module will be called sgi_w1. +config W1_MASTER_PHYTIUM + tristate "Phytium 1-wire driver" + depends on ARCH_PHYTIUM || COMPILE_TEST + help + Say Y here if you want to get support for the 1-wire interface + on an Phytium SoC. + + This driver can also be built as a module. If so, the module + will be called phytium-w1. + endmenu diff --git a/drivers/w1/masters/Makefile b/drivers/w1/masters/Makefile index dae629b7ab49..d7c5ed3723b3 100644 --- a/drivers/w1/masters/Makefile +++ b/drivers/w1/masters/Makefile @@ -12,3 +12,4 @@ obj-$(CONFIG_W1_MASTER_DS1WM) += ds1wm.o obj-$(CONFIG_W1_MASTER_GPIO) += w1-gpio.o obj-$(CONFIG_HDQ_MASTER_OMAP) += omap_hdq.o obj-$(CONFIG_W1_MASTER_SGI) += sgi_w1.o +obj-$(CONFIG_W1_MASTER_PHYTIUM) += phytium_w1.o diff --git a/drivers/w1/masters/phytium_w1.c b/drivers/w1/masters/phytium_w1.c new file mode 100644 index 000000000000..bb15a6b8e09d --- /dev/null +++ b/drivers/w1/masters/phytium_w1.c @@ -0,0 +1,563 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * drivers/w1/masters/phytium_w1m.c + * + * Copyright (C) 2021-2023, Phytium Technology, Co., Ltd. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define PHY_W1M_CTL 0x08 + +/* Simplify mode */ +#define PHY_W1M_CMD 0x04 +#define PHY_W1M_PWM0_START_B 0x30 +#define PHY_W1M_PWM0_END_B 0x34 +#define PHY_W1M_PWM1_START_B 0x38 +#define PHY_W1M_PWM1_END_B 0x3c +#define PHY_W1M_SAMPLE_B 0x40 +#define PHY_W1M_INT_EN_B 0x64 +#define PHY_W1M_INT_STATUS_B 0x74 +#define PHY_W1M_DATA_REG 0x70 + +#define PHY_W1M_CMD_ROM_SEARCH 0xF0 +#define PHY_W1M_CMD_WRITE_BYTE 0x36 +#define PHY_W1M_CMD_RESET_BUS 0x37 +#define PHY_W1M_CMD_READ_BYTE 0x3B +#define PHY_W1M_SLAVE_ROM_ID 0x160 + +#define PHY_W1M_INT_EN_TXCOMPLETE BIT(6) +#define PHY_W1M_INT_EN_RXCOMPLETE BIT(7) + +#define PHY_W1M_INT_STATUS_TXCOMPLETE BIT(6) +#define PHY_W1M_INT_STATUS_RXCOMPLETE BIT(7) + +#define W1M_MOD_W1 1 +#define W1M_MOD_PECI 0 + +#define PHY_W1M_FLAG_CLEAR 0 +#define PHY_W1M_FLAG_SET 1 +#define PHY_W1M_TIMEOUT (HZ/5) + +#define PHY_W1M_MAX_USER 4 + +static DECLARE_WAIT_QUEUE_HEAD(w1m_wait_queue); + +struct w1m_data { + struct device *dev; + void __iomem *w1m_base; + /* lock status update */ + struct mutex w1m_mutex; + int w1m_usecount; + u8 w1m_irqstatus; + /* device lock */ + spinlock_t w1m_spinlock; + /* + * Used to control the call to phytium_w1m_get and phytium_w1m_put. + * Non-w1 Protocol: Write the CMD|REG_address first, followed by + * the data wrire or read. + */ + int init_trans; +}; + +/* W1 register I/O routines */ +static inline u8 phytium_w1m_read(struct w1m_data *w1m_data, u32 offset) +{ + return readl(w1m_data->w1m_base + offset); +} + +static inline void phytium_w1m_write(struct w1m_data *w1m_data, u32 offset, u8 val) +{ + writel(val, w1m_data->w1m_base + offset); +} + +static inline u8 phytium_w1m_merge(struct w1m_data *w1m_data, u32 offset, + u8 val, u8 mask) +{ + u8 new_val = (__raw_readl(w1m_data->w1m_base + offset) & ~mask) + | (val & mask); + writel(new_val, w1m_data->w1m_base + offset); + + return new_val; +} + +static void w1m_disable_interrupt(struct w1m_data *w1m_data, u32 offset, + u32 mask) +{ + u32 ie; + + ie = readl(w1m_data->w1m_base + offset); + writel(ie & mask, w1m_data->w1m_base + offset); +} + +/* write out a byte and fill *status with W1M_INT_STATUS */ +static int phytium_write_byte(struct w1m_data *w1m_data, u8 val, u8 *status) +{ + int ret; + unsigned long irqflags; + + *status = 0; + + spin_lock_irqsave(&w1m_data->w1m_spinlock, irqflags); + /* clear interrupt flags via a dummy read */ + phytium_w1m_read(w1m_data, PHY_W1M_INT_STATUS_B); + /* ISR loads it with new INT_STATUS */ + w1m_data->w1m_irqstatus = 0; + spin_unlock_irqrestore(&w1m_data->w1m_spinlock, irqflags); + + phytium_w1m_merge(w1m_data, PHY_W1M_INT_EN_B, + PHY_W1M_INT_EN_TXCOMPLETE, + PHY_W1M_INT_EN_TXCOMPLETE); + + phytium_w1m_write(w1m_data, PHY_W1M_DATA_REG, val); + phytium_w1m_write(w1m_data, PHY_W1M_CMD, 0x36); + + /* wait for the TXCOMPLETE bit */ + ret = wait_event_timeout(w1m_wait_queue, + w1m_data->w1m_irqstatus, PHY_W1M_TIMEOUT); + if (ret == 0) { + dev_err(w1m_data->dev, "TX wait elapsed\n"); + ret = -ETIMEDOUT; + goto out; + } + + *status = w1m_data->w1m_irqstatus; + /* check irqstatus */ + if (!(*status & PHY_W1M_INT_STATUS_TXCOMPLETE)) { + dev_err(w1m_data->dev, + "timeout waiting for TXCOMPLETE/RXCOMPLETE, %x", *status); + ret = -ETIMEDOUT; + } + +out: + return ret; +} + +static irqreturn_t w1m_isr(int irq, void *_w1m) +{ + struct w1m_data *w1m_data = _w1m; + unsigned long irqflags; + + spin_lock_irqsave(&w1m_data->w1m_spinlock, irqflags); + w1m_data->w1m_irqstatus = phytium_w1m_read(w1m_data, PHY_W1M_INT_STATUS_B); + spin_unlock_irqrestore(&w1m_data->w1m_spinlock, irqflags); + + phytium_w1m_write(w1m_data, PHY_W1M_INT_STATUS_B, 0x00); + if (w1m_data->w1m_irqstatus & + (PHY_W1M_INT_STATUS_TXCOMPLETE | PHY_W1M_INT_STATUS_RXCOMPLETE)) { + /* wake up sleeping process */ + wake_up(&w1m_wait_queue); + } + + return IRQ_HANDLED; +} + +static int phytium_read_byte(struct w1m_data *w1m_data, u8 *val) +{ + int ret = 0; + u8 status; + unsigned long irqflags; + + ret = mutex_lock_interruptible(&w1m_data->w1m_mutex); + if (ret < 0) { + ret = -EINTR; + goto rtn; + } + + if (!w1m_data->w1m_usecount) { + ret = -EINVAL; + goto out; + } + + spin_lock_irqsave(&w1m_data->w1m_spinlock, irqflags); + /* clear interrupt flags via a dummy read */ + phytium_w1m_read(w1m_data, PHY_W1M_INT_STATUS_B); + /* ISR loads it with new INT_STATUS */ + w1m_data->w1m_irqstatus = 0; + spin_unlock_irqrestore(&w1m_data->w1m_spinlock, irqflags); + + phytium_w1m_merge(w1m_data, PHY_W1M_INT_EN_B, + PHY_W1M_INT_EN_RXCOMPLETE, + PHY_W1M_INT_EN_RXCOMPLETE); + + phytium_w1m_write(w1m_data, PHY_W1M_CMD, 0x3b); + + wait_event_timeout(w1m_wait_queue, + (w1m_data->w1m_irqstatus & PHY_W1M_INT_STATUS_RXCOMPLETE), + PHY_W1M_TIMEOUT); + + status = w1m_data->w1m_irqstatus; + /* check irqstatus */ + if (!(status & PHY_W1M_INT_STATUS_RXCOMPLETE)) { + dev_err(w1m_data->dev, "timeout waiting for RXCOMPLETE, %x", status); + ret = -ETIMEDOUT; + goto out; + } + + /* the data is ready. Read it in! */ + *val = phytium_w1m_read(w1m_data, PHY_W1M_DATA_REG); +out: + mutex_unlock(&w1m_data->w1m_mutex); +rtn: + return ret; +} + +static int phytium_w1m_get(struct w1m_data *w1m_data) +{ + int ret = 0; + + ret = mutex_lock_interruptible(&w1m_data->w1m_mutex); + if (ret < 0) { + ret = -EINTR; + goto rtn; + } + + if (w1m_data->w1m_usecount == PHY_W1M_MAX_USER) { + dev_warn(w1m_data->dev, "attempt to exceed the max use count"); + ret = -EINVAL; + goto out; + } else { + w1m_data->w1m_usecount++; + try_module_get(THIS_MODULE); + if (w1m_data->w1m_usecount == 1) + pm_runtime_get_sync(w1m_data->dev); + } + +out: + mutex_unlock(&w1m_data->w1m_mutex); +rtn: + return ret; +} + +/* Disable clocks to the module */ +static int phytium_w1m_put(struct w1m_data *w1m_data) +{ + int ret = 0; + + ret = mutex_lock_interruptible(&w1m_data->w1m_mutex); + if (ret < 0) + return -EINTR; + + if (w1m_data->w1m_usecount == 0) { + dev_warn(w1m_data->dev, + "attempt to decrement use count when it is zero"); + ret = -EINVAL; + } else { + w1m_data->w1m_usecount--; + module_put(THIS_MODULE); + if (w1m_data->w1m_usecount == 0) + pm_runtime_put_sync(w1m_data->dev); + } + mutex_unlock(&w1m_data->w1m_mutex); + + return ret; +} + +/* + * W1 triplet callback function - used for searching ROM addresses. + * Registered only when controller is in 1-wire mode. + */ +static u8 phytium_w1_triplet(void *_w1m, u8 bdir) +{ + u8 id_bit, comp_bit; + int err; + u8 ret = 0x3; /* no slaves responded */ + struct w1m_data *w1m_data = _w1m; + + phytium_w1m_get(_w1m); + + err = mutex_lock_interruptible(&w1m_data->w1m_mutex); + if (err < 0) { + dev_err(w1m_data->dev, "Could not acquire mutex\n"); + goto rtn; + } + + w1m_data->w1m_irqstatus = 0; + /* read id_bit */ + phytium_w1m_merge(w1m_data, PHY_W1M_INT_EN_B, + PHY_W1M_INT_EN_RXCOMPLETE, + PHY_W1M_INT_EN_RXCOMPLETE); + phytium_w1m_write(w1m_data, PHY_W1M_CMD, 0x3a); + + err = wait_event_timeout(w1m_wait_queue, + (w1m_data->w1m_irqstatus + & PHY_W1M_INT_STATUS_RXCOMPLETE), + PHY_W1M_TIMEOUT); + if (err == 0) { + dev_err(w1m_data->dev, "RX wait elapsed\n"); + goto out; + } + id_bit = (phytium_w1m_read(_w1m, PHY_W1M_DATA_REG) & 0x01); + + w1m_data->w1m_irqstatus = 0; + /* read comp_bit */ + phytium_w1m_merge(w1m_data, PHY_W1M_INT_EN_B, + PHY_W1M_INT_EN_RXCOMPLETE, + PHY_W1M_INT_EN_RXCOMPLETE); + phytium_w1m_write(w1m_data, PHY_W1M_CMD, 0x3a); + err = wait_event_timeout(w1m_wait_queue, + (w1m_data->w1m_irqstatus + & PHY_W1M_INT_STATUS_RXCOMPLETE), + PHY_W1M_TIMEOUT); + if (err == 0) { + dev_err(w1m_data->dev, "RX wait elapsed\n"); + goto out; + } + comp_bit = (phytium_w1m_read(_w1m, PHY_W1M_DATA_REG) & 0x01); + + if (id_bit && comp_bit) { + ret = 0x03; /* no slaves responded */ + goto out; + } + if (!id_bit && !comp_bit) { + /* Both bits are valid, take the direction given */ + ret = bdir ? 0x04 : 0; + } else { + /* Only one bit is valid, take that direction */ + bdir = id_bit; + ret = id_bit ? 0x05 : 0x02; + } + + w1m_data->w1m_irqstatus = 0; + /* write bdir bit */ + phytium_w1m_merge(w1m_data, PHY_W1M_INT_EN_B, + PHY_W1M_INT_EN_TXCOMPLETE, + PHY_W1M_INT_EN_TXCOMPLETE); + + phytium_w1m_write(w1m_data, PHY_W1M_DATA_REG, bdir); + phytium_w1m_write(w1m_data, PHY_W1M_CMD, 0x35); + + err = wait_event_timeout(w1m_wait_queue, + (w1m_data->w1m_irqstatus + & PHY_W1M_INT_STATUS_TXCOMPLETE), + PHY_W1M_TIMEOUT); + if (err == 0) { + dev_err(w1m_data->dev, "TX wait elapsed\n"); + goto out; + } + +out: + mutex_unlock(&w1m_data->w1m_mutex); +rtn: + phytium_w1m_put(_w1m); + return ret; +} + +/* reset callback */ +static u8 phytium_w1_reset_bus(void *_w1m) +{ + phytium_w1m_get(_w1m); + phytium_w1m_write(_w1m, PHY_W1M_CMD, 0x37); + mdelay(1); + phytium_w1m_put(_w1m); + return 0; +} + +/* Read a byte of data from the device */ +static u8 phytium_w1_read_byte(void *_w1m) +{ + struct w1m_data *w1m_data = _w1m; + u8 val = 0; + int ret; + + /* First write to initialize the transfer */ + if (w1m_data->init_trans == 0) + phytium_w1m_get(w1m_data); + + w1m_data->init_trans++; + ret = phytium_read_byte(w1m_data, &val); + if (ret) { + ret = mutex_lock_interruptible(&w1m_data->w1m_mutex); + if (ret < 0) { + dev_err(w1m_data->dev, "Could not acquire mutex\n"); + return -EINTR; + } + w1m_data->init_trans = 0; + mutex_unlock(&w1m_data->w1m_mutex); + phytium_w1m_put(w1m_data); + return -1; + } + + w1m_disable_interrupt(w1m_data, PHY_W1M_INT_EN_B, + ~((u32)PHY_W1M_INT_EN_RXCOMPLETE)); + + /* Write followed by a read, release the module */ + if (w1m_data->init_trans) { + ret = mutex_lock_interruptible(&w1m_data->w1m_mutex); + if (ret < 0) { + dev_err(w1m_data->dev, "Could not acquire mutex\n"); + return -EINTR; + } + w1m_data->init_trans = 0; + mutex_unlock(&w1m_data->w1m_mutex); + phytium_w1m_put(w1m_data); + } + + return val; +} + +/* Write a byte of data to the device */ +static void phytium_w1_write_byte(void *_w1m, u8 byte) +{ + struct w1m_data *w1m_data = _w1m; + int ret; + u8 status; + + /* First write to initialize the transfer */ + if (w1m_data->init_trans == 0) + phytium_w1m_get(w1m_data); + + ret = mutex_lock_interruptible(&w1m_data->w1m_mutex); + if (ret < 0) { + dev_err(w1m_data->dev, "Could not acquire mutex\n"); + return; + } + mutex_unlock(&w1m_data->w1m_mutex); + + ret = phytium_write_byte(w1m_data, byte, &status); + if (ret < 0) { + dev_err(w1m_data->dev, "TX failure:Ctrl status %x\n", status); + return; + } + + w1m_data->init_trans++; + w1m_disable_interrupt(w1m_data, PHY_W1M_INT_EN_B, + ~((u32)PHY_W1M_INT_EN_TXCOMPLETE)); + /* Second write, data transferred. Release the module */ + if (w1m_data->init_trans > 1) { + phytium_w1m_put(w1m_data); + ret = mutex_lock_interruptible(&w1m_data->w1m_mutex); + if (ret < 0) { + dev_err(w1m_data->dev, "Could not acquire mutex\n"); + return; + } + w1m_data->init_trans = 0; + mutex_unlock(&w1m_data->w1m_mutex); + } +} + +static struct w1_bus_master phytium_w1_master = { + .read_byte = phytium_w1_read_byte, + .write_byte = phytium_w1_write_byte, + .reset_bus = phytium_w1_reset_bus, +}; + +static int phytium_w1m_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct w1m_data *w1m_data; + struct resource *res; + int ret, irq; + + w1m_data = devm_kzalloc(dev, sizeof(*w1m_data), GFP_KERNEL); + if (!w1m_data) + return -ENOMEM; + + w1m_data->dev = dev; + platform_set_drvdata(pdev, w1m_data); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + w1m_data->w1m_base = devm_ioremap_resource(dev, res); + if (IS_ERR(w1m_data->w1m_base)) + return PTR_ERR(w1m_data->w1m_base); + + w1m_data->w1m_usecount = 0; + mutex_init(&w1m_data->w1m_mutex); + + pm_runtime_enable(&pdev->dev); + ret = pm_runtime_get_sync(&pdev->dev); + if (ret < 0) { + dev_err(&pdev->dev, "pm_runtime_get_sync failed\n"); + goto err_w1; + } + + spin_lock_init(&w1m_data->w1m_spinlock); + + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + dev_err(&pdev->dev, "Failed to get IRQ: %d\n", irq); + ret = irq; + goto err_irq; + } + + ret = devm_request_irq(dev, irq, w1m_isr, 0, "phytium-w1", w1m_data); + if (ret < 0) { + dev_err(&pdev->dev, "could not request irq\n"); + goto err_irq; + } + + pm_runtime_put_sync(&pdev->dev); + + phytium_w1_master.triplet = phytium_w1_triplet; + phytium_w1m_write(w1m_data, PHY_W1M_CTL, 0x10); + phytium_w1m_write(w1m_data, PHY_W1M_INT_EN_B, 0x00); + + phytium_w1_master.data = w1m_data; + + ret = w1_add_master_device(&phytium_w1_master); + if (ret) { + dev_err(&pdev->dev, "Failure in registering w1 master\n"); + goto err_w1; + } + + return 0; + +err_irq: + pm_runtime_put_sync(&pdev->dev); +err_w1: + pm_runtime_disable(&pdev->dev); + + return ret; +} + +static int phytium_w1m_remove(struct platform_device *pdev) +{ + struct w1m_data *w1m_data = platform_get_drvdata(pdev); + + mutex_lock(&w1m_data->w1m_mutex); + + if (w1m_data->w1m_usecount) { + dev_warn(&pdev->dev, "removed when use count is not zero\n"); + mutex_unlock(&w1m_data->w1m_mutex); + return -EBUSY; + } + + mutex_unlock(&w1m_data->w1m_mutex); + + /* remove module dependency */ + pm_runtime_disable(&pdev->dev); + + return 0; +} + +static const struct of_device_id phytium_w1m_dt_ids[] = { + { .compatible = "phytium,w1" }, + { } +}; +MODULE_DEVICE_TABLE(of, phytium_w1m_dt_ids); + +static struct platform_driver phytium_w1m_driver = { + .probe = phytium_w1m_probe, + .remove = phytium_w1m_remove, + .driver = { + .name = "phytium-w1", + .of_match_table = phytium_w1m_dt_ids, + }, +}; +module_platform_driver(phytium_w1m_driver); + +MODULE_AUTHOR("Zhu Mingshuai "); +MODULE_DESCRIPTION("Phytium w1 bus master driver"); +MODULE_LICENSE("GPL");