add Phytium w1 bus master driver

This patch adds support for the 1-wire master interface of Phytium.

Signed-off-by: wangzhimin <wangzhimin1179@phytium.com.cn>
(cherry picked from commit 06cac346ea)
Signed-off-by: Alex Shi <alexsshi@tencent.com>
This commit is contained in:
wangzhimin 2023-06-07 07:05:22 +00:00 committed by Jianping Liu
parent e0faf0d0f4
commit f17c53e316
3 changed files with 574 additions and 0 deletions

View File

@ -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

View File

@ -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

View File

@ -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 <linux/kernel.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/interrupt.h>
#include <linux/slab.h>
#include <linux/err.h>
#include <linux/io.h>
#include <linux/sched.h>
#include <linux/pm_runtime.h>
#include <linux/of.h>
#include <linux/delay.h>
#include <linux/w1.h>
#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 <zhumingshuai@phytium.com.cn>");
MODULE_DESCRIPTION("Phytium w1 bus master driver");
MODULE_LICENSE("GPL");