383 lines
9.6 KiB
C
383 lines
9.6 KiB
C
|
/* SPDX-License-Identifier: GPL-2.0 */
|
||
|
/*
|
||
|
* Copyright (c) 2021 MediaTek Inc.
|
||
|
*/
|
||
|
|
||
|
#include <linux/init.h>
|
||
|
#include <linux/module.h>
|
||
|
#include <linux/types.h>
|
||
|
#include <linux/device.h>
|
||
|
#include <linux/of.h>
|
||
|
#include <linux/spi/spi.h>
|
||
|
#include <linux/mutex.h>
|
||
|
#include <linux/platform_device.h>
|
||
|
#include <mtk_spi.h>
|
||
|
#include <linux/delay.h>
|
||
|
#include <linux/of_irq.h>
|
||
|
#include <linux/interrupt.h>
|
||
|
#ifdef CONFIG_RT_REGMAP
|
||
|
#include <mt-plat/rt-regmap.h>
|
||
|
#endif /* CONFIG_RT_REGMAP */
|
||
|
#include "isl91302a-spi.h"
|
||
|
|
||
|
#define ISL91302A_DRV_VERSION "1.0.0_M"
|
||
|
static int isl91302a_read_device(void *client, u32 addr, int len, void *dst)
|
||
|
{
|
||
|
int ret = 0;
|
||
|
struct spi_device *spi = (struct spi_device *)client;
|
||
|
struct spi_transfer xfer = {0,}; /* must init spi_transfer here */
|
||
|
struct spi_message msg;
|
||
|
u32 tx_buf;
|
||
|
u32 rx_buf;
|
||
|
|
||
|
if (len != 1) {
|
||
|
pr_notice("%s not support multi read now\n", __func__);
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
/* LSM first, TX: buf, size, reg, 89 */
|
||
|
tx_buf = 0x00010080;
|
||
|
rx_buf = 0xffffffff;
|
||
|
|
||
|
tx_buf |= addr << 8;
|
||
|
|
||
|
xfer.tx_buf = &tx_buf;
|
||
|
xfer.rx_buf = &rx_buf;
|
||
|
xfer.len = 4;
|
||
|
|
||
|
spi_message_init(&msg);
|
||
|
spi_message_add_tail(&xfer, &msg);
|
||
|
ret = spi_sync(spi, &msg);
|
||
|
if (ret < 0 || rx_buf == 0xffffffff)
|
||
|
return ret;
|
||
|
|
||
|
*(unsigned char *)dst = (rx_buf & 0xff000000) >> 24;
|
||
|
#if 0
|
||
|
pr_info("%s addr 0x%02x = 0x%02x\n", __func__, addr, *val);
|
||
|
pr_info("%s tx_buf = 0x%08x\n", __func__, tx_buf);
|
||
|
pr_info("%s rx_buf = 0x%08x\n", __func__, rx_buf);
|
||
|
#endif
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
int isl91302a_write_device(void *client,
|
||
|
uint32_t addr, int len, const void *src)
|
||
|
{
|
||
|
int ret = 0;
|
||
|
struct spi_device *spi = (struct spi_device *)client;
|
||
|
struct spi_transfer xfer = {0,}; /* must init spi_transfer here */
|
||
|
struct spi_message msg;
|
||
|
unsigned char regval;
|
||
|
u32 tx_buf;
|
||
|
u32 rx_buf;
|
||
|
|
||
|
if (len != 1) {
|
||
|
pr_notice("%s not support multi write now\n", __func__);
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
tx_buf = 0x00010000;
|
||
|
rx_buf = 0xffffffff;
|
||
|
|
||
|
regval = *(unsigned char *)src;
|
||
|
tx_buf |= ((regval << 24) | (addr << 8));
|
||
|
|
||
|
xfer.tx_buf = &tx_buf;
|
||
|
xfer.rx_buf = &rx_buf;
|
||
|
xfer.len = 4;
|
||
|
|
||
|
spi_message_init(&msg);
|
||
|
spi_message_add_tail(&xfer, &msg);
|
||
|
ret = spi_sync(spi, &msg);
|
||
|
if (ret < 0 || rx_buf == 0xffffffff)
|
||
|
return ret;
|
||
|
|
||
|
#if 0
|
||
|
pr_info("%s addr 0x%02x = 0x%02x\n", __func__, addr, value);
|
||
|
pr_info("%s tx_buf = 0x%08x\n", __func__, tx_buf);
|
||
|
pr_info("%s rx_buf = 0x%08x\n", __func__, rx_buf);
|
||
|
#endif
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
#ifdef CONFIG_RT_REGMAP
|
||
|
RT_REG_DECL(ISL91302A_CHIPNAME_R, 1, RT_VOLATILE, {});
|
||
|
RT_REG_DECL(ISL91302A_FLT_RECORDTEMP_R, 1, RT_VOLATILE, {});
|
||
|
RT_REG_DECL(ISL91302A_MODECTRL_R, 1, RT_VOLATILE, {});
|
||
|
RT_REG_DECL(ISL91302A_IRQ_MASK_R, 1, RT_VOLATILE, {});
|
||
|
RT_REG_DECL(ISL91302A_BUCK1_DCM_R, 1, RT_VOLATILE, {});
|
||
|
RT_REG_DECL(ISL91302A_BUCK1_UP_R, 1, RT_VOLATILE, {});
|
||
|
RT_REG_DECL(ISL91302A_BUCK1_LO_R, 1, RT_VOLATILE, {});
|
||
|
RT_REG_DECL(ISL91302A_BUCK1_RSPCFG1_R, 1, RT_VOLATILE, {});
|
||
|
RT_REG_DECL(ISL91302A_BUCK2_DCM_R, 1, RT_VOLATILE, {});
|
||
|
RT_REG_DECL(ISL91302A_BUCK2_UP_R, 1, RT_VOLATILE, {});
|
||
|
RT_REG_DECL(ISL91302A_BUCK2_LO_R, 1, RT_VOLATILE, {});
|
||
|
RT_REG_DECL(ISL91302A_BUCK2_RSPCFG1_R, 1, RT_VOLATILE, {});
|
||
|
RT_REG_DECL(ISL91302A_BUCK3_DCM_R, 1, RT_VOLATILE, {});
|
||
|
RT_REG_DECL(ISL91302A_BUCK3_UP_R, 1, RT_VOLATILE, {});
|
||
|
RT_REG_DECL(ISL91302A_BUCK3_LO_R, 1, RT_VOLATILE, {});
|
||
|
RT_REG_DECL(ISL91302A_BUCK3_RSPCFG1_R, 1, RT_VOLATILE, {});
|
||
|
|
||
|
static const rt_register_map_t isl91302a_regmap[] = {
|
||
|
RT_REG(ISL91302A_CHIPNAME_R),
|
||
|
RT_REG(ISL91302A_FLT_RECORDTEMP_R),
|
||
|
RT_REG(ISL91302A_MODECTRL_R),
|
||
|
RT_REG(ISL91302A_IRQ_MASK_R),
|
||
|
RT_REG(ISL91302A_BUCK1_DCM_R),
|
||
|
RT_REG(ISL91302A_BUCK1_UP_R),
|
||
|
RT_REG(ISL91302A_BUCK1_LO_R),
|
||
|
RT_REG(ISL91302A_BUCK1_RSPCFG1_R),
|
||
|
RT_REG(ISL91302A_BUCK2_DCM_R),
|
||
|
RT_REG(ISL91302A_BUCK2_UP_R),
|
||
|
RT_REG(ISL91302A_BUCK2_LO_R),
|
||
|
RT_REG(ISL91302A_BUCK2_RSPCFG1_R),
|
||
|
RT_REG(ISL91302A_BUCK3_DCM_R),
|
||
|
RT_REG(ISL91302A_BUCK3_UP_R),
|
||
|
RT_REG(ISL91302A_BUCK3_LO_R),
|
||
|
RT_REG(ISL91302A_BUCK3_RSPCFG1_R),
|
||
|
};
|
||
|
|
||
|
static struct rt_regmap_properties isl91302a_regmap_props = {
|
||
|
.name = "isl91302a",
|
||
|
.aliases = "isl91302a",
|
||
|
.register_num = ARRAY_SIZE(isl91302a_regmap),
|
||
|
.rm = isl91302a_regmap,
|
||
|
.rt_regmap_mode = RT_CACHE_DISABLE,
|
||
|
};
|
||
|
|
||
|
static struct rt_regmap_fops isl91302a_regmap_fops = {
|
||
|
.read_device = isl91302a_read_device,
|
||
|
.write_device = isl91302a_write_device,
|
||
|
};
|
||
|
#endif /* CONFIG_RT_REGMAP */
|
||
|
|
||
|
int isl91302a_read_byte(void *client, uint32_t addr, uint32_t *value)
|
||
|
{
|
||
|
int ret;
|
||
|
struct spi_device *spi = (struct spi_device *)client;
|
||
|
struct isl91302a_chip *chip = spi_get_drvdata(spi);
|
||
|
|
||
|
#ifdef CONFIG_RT_REGMAP
|
||
|
ret = rt_regmap_block_read(chip->regmap_dev, addr, 1, value);
|
||
|
#else
|
||
|
ret = isl91302a_read_device(chip->spi, addr, 1, value);
|
||
|
#endif /* CONFIG_RT_REGMAP */
|
||
|
if (ret < 0)
|
||
|
pr_notice("%s read addr0x%02x fail\n", __func__, addr);
|
||
|
return ret;
|
||
|
}
|
||
|
EXPORT_SYMBOL(isl91302a_read_byte);
|
||
|
|
||
|
int isl91302a_write_byte(void *client, uint32_t addr, uint32_t data)
|
||
|
{
|
||
|
int ret = 0;
|
||
|
struct spi_device *spi = (struct spi_device *)client;
|
||
|
struct isl91302a_chip *chip = spi_get_drvdata(spi);
|
||
|
|
||
|
#ifdef CONFIG_RT_REGMAP
|
||
|
ret = rt_regmap_block_write(chip->regmap_dev, addr, 1, &data);
|
||
|
#else
|
||
|
ret = isl91302a_write_device(chip->spi, addr, 1, &data);
|
||
|
#endif /* CONFIG_RT_REGMAP */
|
||
|
if (ret < 0)
|
||
|
pr_notice("%s write addr0x%02x fail\n", __func__, addr);
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
int isl91302a_assign_bit(void *client,
|
||
|
uint32_t reg, uint32_t mask, uint32_t data)
|
||
|
{
|
||
|
int ret = 0;
|
||
|
struct spi_device *spi = (struct spi_device *)client;
|
||
|
struct isl91302a_chip *ri = spi_get_drvdata(spi);
|
||
|
unsigned char tmp = 0;
|
||
|
uint32_t regval = 0;
|
||
|
|
||
|
mutex_lock(&ri->io_lock);
|
||
|
ret = isl91302a_read_byte(spi, reg, ®val);
|
||
|
if (ret < 0) {
|
||
|
pr_notice("%s fail reg0x%02x data0x%02x\n",
|
||
|
__func__, reg, data);
|
||
|
goto OUT_ASSIGN;
|
||
|
}
|
||
|
tmp = ((regval & 0xff) & ~mask);
|
||
|
tmp |= (data & mask);
|
||
|
ret = isl91302a_write_byte(spi, reg, tmp);
|
||
|
if (ret < 0)
|
||
|
pr_notice("%s fail reg0x%02x data0x%02x\n",
|
||
|
__func__, reg, tmp);
|
||
|
OUT_ASSIGN:
|
||
|
mutex_unlock(&ri->io_lock);
|
||
|
return ret;
|
||
|
}
|
||
|
EXPORT_SYMBOL(isl91302a_assign_bit);
|
||
|
|
||
|
static struct mt_chip_conf isl91302a_spi_config = {
|
||
|
/* setuptime, holdtime --> timing for waveform of SPI */
|
||
|
.setuptime = 3,
|
||
|
.holdtime = 3,
|
||
|
/* high_time, low_time --> set SCK */
|
||
|
.high_time = 10,
|
||
|
.low_time = 10,
|
||
|
/* CS pin idle time */
|
||
|
.cs_idletime = 2,
|
||
|
/* cpol, cpha -->set type */
|
||
|
.cpol = 0,
|
||
|
.cpha = 0,
|
||
|
/* rx_mlsb, tx_mlsb --> MSB first or not */
|
||
|
.rx_mlsb = 1,
|
||
|
.tx_mlsb = 1,
|
||
|
/* tx_endian, rx_endian -->
|
||
|
* Defines whether to reverse the endian order
|
||
|
*/
|
||
|
.tx_endian = 0,
|
||
|
.rx_endian = 0,
|
||
|
/* com_mod --> FIFO/DMA mode */
|
||
|
.com_mod = FIFO_TRANSFER,
|
||
|
/* pause --> if want to always let CS active, set this flag to 1*/
|
||
|
.pause = 0,
|
||
|
/* tckdly --> tune timing */
|
||
|
.tckdly = 0,
|
||
|
|
||
|
/* ?? */
|
||
|
.ulthgh_thrsh = 0,
|
||
|
.cs_pol = 0,
|
||
|
.sample_sel = 0,
|
||
|
.finish_intr = 1,
|
||
|
.deassert = 0,
|
||
|
.ulthigh = 0,
|
||
|
};
|
||
|
|
||
|
static void isl91302a_spi_init(struct spi_device *spi)
|
||
|
{
|
||
|
pr_info("%s inited\n", __func__);
|
||
|
spi->bits_per_word = 32;
|
||
|
spi->controller_data = &isl91302a_spi_config;
|
||
|
mdelay(100);
|
||
|
}
|
||
|
|
||
|
static void isl91302a_reg_init(struct spi_device *spi)
|
||
|
{
|
||
|
int ret = 0;
|
||
|
|
||
|
/* Sleeping voltage inti setting for SPM*/
|
||
|
/* VDVFS1 */
|
||
|
ret = isl91302a_write_byte(spi, 0x7e, 0x76);
|
||
|
ret |= isl91302a_write_byte(spi, 0x7f, 0x00);
|
||
|
/* VDVFS2 */
|
||
|
ret |= isl91302a_write_byte(spi, 0x64, 0x76);
|
||
|
ret |= isl91302a_write_byte(spi, 0x65, 0x00);
|
||
|
if (ret < 0)
|
||
|
ISL91302A_pr_notice("%s init fail\n", __func__);
|
||
|
}
|
||
|
|
||
|
static int isl91302a_check_id(struct spi_device *spi)
|
||
|
{
|
||
|
int ret;
|
||
|
unsigned char data;
|
||
|
|
||
|
ret = isl91302a_read_device(spi, ISL91302A_CHIPNAME_R, 1, &data);
|
||
|
if (ret < 0) {
|
||
|
pr_notice("%s IO fail\n", __func__);
|
||
|
return -EIO;
|
||
|
}
|
||
|
|
||
|
if (data != ISL91302A_CHIPNAME) {
|
||
|
pr_notice("%s ID(0x%02x) not match\n", __func__, data);
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int isl91302a_spi_probe(struct spi_device *spi)
|
||
|
{
|
||
|
struct isl91302a_chip *chip;
|
||
|
int ret;
|
||
|
|
||
|
pr_info("%s\n", __func__);
|
||
|
|
||
|
chip = devm_kzalloc(&spi->dev,
|
||
|
sizeof(struct isl91302a_chip), GFP_KERNEL);
|
||
|
|
||
|
chip->spi = spi;
|
||
|
chip->dev = &spi->dev;
|
||
|
mutex_init(&chip->io_lock);
|
||
|
spi_set_drvdata(spi, chip);
|
||
|
|
||
|
isl91302a_spi_init(spi);
|
||
|
|
||
|
ret = isl91302a_check_id(spi);
|
||
|
if (ret < 0)
|
||
|
return ret;
|
||
|
|
||
|
#ifdef CONFIG_RT_REGMAP
|
||
|
chip->regmap_dev = rt_regmap_device_register_ex(&isl91302a_regmap_props,
|
||
|
&isl91302a_regmap_fops, &spi->dev, spi, -1, chip);
|
||
|
if (!chip->regmap_dev) {
|
||
|
pr_notice("%s register regmap fail\n", __func__);
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
#endif /* #ifdef CONFIG_RT_REGMAP */
|
||
|
|
||
|
isl91302a_reg_init(spi);
|
||
|
|
||
|
ret = isl91302a_regulator_init(chip);
|
||
|
if (ret < 0) {
|
||
|
ISL91302A_pr_notice("%s regulator init fail\n", __func__);
|
||
|
#ifdef CONFIG_RT_REGMAP
|
||
|
rt_regmap_device_unregister(chip->regmap_dev);
|
||
|
#endif /* CONFIG_RT_REGMAP */
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
pr_info("%s --OK!!--\n", __func__);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int isl91302a_spi_remove(struct spi_device *spi)
|
||
|
{
|
||
|
struct isl91302a_chip *chip = spi_get_drvdata(spi);
|
||
|
|
||
|
if (chip) {
|
||
|
isl91302a_regulator_deinit(chip);
|
||
|
mutex_destroy(&chip->io_lock);
|
||
|
#ifdef CONFIG_RT_REGMAP
|
||
|
rt_regmap_device_unregister(chip->regmap_dev);
|
||
|
#endif /* #ifdef CONFIG_RT_REGMAP */
|
||
|
}
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static const struct of_device_id isl91302a_id_table[] = {
|
||
|
{.compatible = "mediatek,isl91302a",},
|
||
|
};
|
||
|
|
||
|
static struct spi_driver isl91302a_spi_driver = {
|
||
|
.driver = {
|
||
|
.name = "isl91302a",
|
||
|
.bus = &spi_bus_type,
|
||
|
.owner = THIS_MODULE,
|
||
|
.of_match_table = isl91302a_id_table,
|
||
|
},
|
||
|
.probe = isl91302a_spi_probe,
|
||
|
.remove = isl91302a_spi_remove,
|
||
|
};
|
||
|
|
||
|
static int __init isl91302a_init(void)
|
||
|
{
|
||
|
pr_notice("%s ver(%s)\n", __func__, ISL91302A_DRV_VERSION);
|
||
|
return spi_register_driver(&isl91302a_spi_driver);
|
||
|
}
|
||
|
|
||
|
static void __exit isl91302a_exit(void)
|
||
|
{
|
||
|
spi_unregister_driver(&isl91302a_spi_driver);
|
||
|
}
|
||
|
subsys_initcall(isl91302a_init);
|
||
|
module_exit(isl91302a_exit);
|
||
|
|
||
|
MODULE_DESCRIPTION("ISL91302A Regulator Driver");
|
||
|
MODULE_VERSION(ISL91302A_DRV_VERSION);
|
||
|
MODULE_AUTHOR("Sakya <jeff_chang@richtek.com>");
|
||
|
MODULE_LICENSE("GPL");
|