Skip to content
Permalink
Browse files
misc: Add Renesas Synchronization Management Unit (SMU) support
This driver is developed for the IDT ClockMatrix(TM) and 82P33xxx families
of timing and synchronization devices.It will be used by Renesas PTP Clock
Manager for Linux (pcm4l) software to provide support to GNSS assisted
partial timing support (APTS) and other networking timing functions.

Current version provides kernel API's to support the following functions
-set combomode to enable SYNCE clock support
-read dpll's state to determine if the dpll is locked to the GNSS channel
-read dpll's ffo (fractional frequency offset)

Signed-off-by: Min Li <min.li.xe@renesas.com>
  • Loading branch information
Min Li authored and intel-lab-lkp committed Mar 17, 2021
1 parent ebfbc46 commit 2b3ca22001b1bb3ea6466c970c9026457d5b6fa3
Show file tree
Hide file tree
Showing 7 changed files with 775 additions and 0 deletions.
@@ -445,6 +445,16 @@ config HISI_HIKEY_USB
switching between the dual-role USB-C port and the USB-A host ports
using only one USB controller.

config RSMU
tristate "Renesas Synchronization Management Unit (SMU)"
depends on MFD_RSMU_I2C || MFD_RSMU_SPI
help
This option enables support for the IDT ClockMatrix(TM) and 82P33xxx
families of timing and synchronization devices.It will be used by
Renesas PTP Clock Manager for Linux (pcm4l) software to provide support
for GNSS assisted partial timing support (APTS) and other networking
timing functions.

source "drivers/misc/c2port/Kconfig"
source "drivers/misc/eeprom/Kconfig"
source "drivers/misc/cb710/Kconfig"
@@ -56,3 +56,5 @@ obj-$(CONFIG_HABANA_AI) += habanalabs/
obj-$(CONFIG_UACCE) += uacce/
obj-$(CONFIG_XILINX_SDFEC) += xilinx_sdfec.o
obj-$(CONFIG_HISI_HIKEY_USB) += hisi_hikey_usb.o
rsmu-objs := rsmu_cdev.o rsmu_cm.o rsmu_sabre.o
obj-$(CONFIG_RSMU) += rsmu.o
@@ -0,0 +1,336 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* This driver is developed for the IDT ClockMatrix(TM) and 82P33xxx families
* of timing and synchronization devices.It will be used by Renesas PTP Clock
* Manager for Linux (pcm4l) software to provide support to GNSS assisted
* partial timing support (APTS) and other networking timing functions.
*
* Please note it must work with Renesas MFD driver to access device through
* I2C/SPI.
*
* Copyright (C) 2019 Integrated Device Technology, Inc., a Renesas Company.
*/

#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt

#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/fs.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/mfd/rsmu.h>
#include <uapi/linux/rsmu.h>

#include "rsmu_cdev.h"

#define DRIVER_NAME "rsmu"
#define DRIVER_MAX_DEV BIT(MINORBITS)

static struct class *rsmu_class;
static dev_t rsmu_cdevt;
static struct rsmu_ops *ops_array[] = {
[RSMU_CM] = &cm_ops,
[RSMU_SABRE] = &sabre_ops,
};

static int
rsmu_set_combomode(struct rsmu_cdev *rsmu, void __user *arg)
{
struct rsmu_ops *ops = rsmu->ops;
struct rsmu_combomode mode;
int err;

if (copy_from_user(&mode, arg, sizeof(mode)))
return -EFAULT;

if (ops->set_combomode == NULL)
return -ENOTSUPP;

mutex_lock(rsmu->lock);
err = ops->set_combomode(rsmu, mode.dpll, mode.mode);
mutex_unlock(rsmu->lock);

return err;
}

static int
rsmu_get_dpll_state(struct rsmu_cdev *rsmu, void __user *arg)
{
struct rsmu_ops *ops = rsmu->ops;
struct rsmu_get_state state_request;
u8 state;
int err;

if (copy_from_user(&state_request, arg, sizeof(state_request)))
return -EFAULT;

if (ops->get_dpll_state == NULL)
return -ENOTSUPP;

mutex_lock(rsmu->lock);
err = ops->get_dpll_state(rsmu, state_request.dpll, &state);
mutex_unlock(rsmu->lock);

state_request.state = state;
if (copy_to_user(arg, &state_request, sizeof(state_request)))
return -EFAULT;

return err;
}

static int
rsmu_get_dpll_ffo(struct rsmu_cdev *rsmu, void __user *arg)
{
struct rsmu_ops *ops = rsmu->ops;
struct rsmu_get_ffo ffo_request;
int err;

if (copy_from_user(&ffo_request, arg, sizeof(ffo_request)))
return -EFAULT;

if (ops->get_dpll_ffo == NULL)
return -ENOTSUPP;

mutex_lock(rsmu->lock);
err = ops->get_dpll_ffo(rsmu, ffo_request.dpll, &ffo_request);
mutex_unlock(rsmu->lock);

if (copy_to_user(arg, &ffo_request, sizeof(ffo_request)))
return -EFAULT;

return err;
}

static int
rsmu_open(struct inode *iptr, struct file *fptr)
{
struct rsmu_cdev *rsmu;

rsmu = container_of(iptr->i_cdev, struct rsmu_cdev, rsmu_cdev);
if (!rsmu)
return -EAGAIN;

fptr->private_data = rsmu;
return 0;
}

static int
rsmu_release(struct inode *iptr, struct file *fptr)
{
struct rsmu_cdev *rsmu;

rsmu = container_of(iptr->i_cdev, struct rsmu_cdev, rsmu_cdev);
if (!rsmu)
return -EAGAIN;

return 0;
}

static long
rsmu_ioctl(struct file *fptr, unsigned int cmd, unsigned long data)
{
struct rsmu_cdev *rsmu = fptr->private_data;
void __user *arg = (void __user *)data;
int err = 0;

if (!rsmu)
return -EINVAL;

switch (cmd) {
case RSMU_SET_COMBOMODE:
err = rsmu_set_combomode(rsmu, arg);
break;
case RSMU_GET_STATE:
err = rsmu_get_dpll_state(rsmu, arg);
break;
case RSMU_GET_FFO:
err = rsmu_get_dpll_ffo(rsmu, arg);
break;
default:
/* Should not get here */
dev_err(rsmu->dev, "Undefined RSMU IOCTL");
err = -EINVAL;
break;
}

return err;
}

static long rsmu_compat_ioctl(struct file *fptr, unsigned int cmd,
unsigned long data)
{
return rsmu_ioctl(fptr, cmd, data);
}

static const struct file_operations rsmu_fops = {
.owner = THIS_MODULE,
.open = rsmu_open,
.release = rsmu_release,
.unlocked_ioctl = rsmu_ioctl,
.compat_ioctl = rsmu_compat_ioctl,
};

static int rsmu_init_ops(struct rsmu_cdev *rsmu)
{
int i;

for (i = 0; i < ARRAY_SIZE(ops_array); i++)
if (ops_array[i]->type == rsmu->type)
break;

if (i == ARRAY_SIZE(ops_array))
return -EINVAL;

rsmu->ops = ops_array[i];
return 0;
}

static int
rsmu_probe(struct platform_device *pdev)
{
struct rsmu_pdata *pdata = dev_get_platdata(&pdev->dev);
struct rsmu_cdev *rsmu;
struct device *rsmu_cdev;
int err;

rsmu = devm_kzalloc(&pdev->dev, sizeof(*rsmu), GFP_KERNEL);
if (!rsmu)
return -ENOMEM;

rsmu->dev = &pdev->dev;
rsmu->mfd = pdev->dev.parent;
rsmu->type = pdata->type;
rsmu->lock = pdata->lock;
rsmu->index = pdata->index;

/* Save driver private data */
platform_set_drvdata(pdev, rsmu);

cdev_init(&rsmu->rsmu_cdev, &rsmu_fops);
rsmu->rsmu_cdev.owner = THIS_MODULE;
err = cdev_add(&rsmu->rsmu_cdev,
MKDEV(MAJOR(rsmu_cdevt), 0), 1);
if (err < 0) {
dev_err(rsmu->dev, "cdev_add failed");
err = -EIO;
goto err_rsmu_dev;
}

if (!rsmu_class) {
err = -EIO;
dev_err(rsmu->dev, "rsmu class not created correctly");
goto err_rsmu_cdev;
}

rsmu_cdev = device_create(rsmu_class, rsmu->dev,
MKDEV(MAJOR(rsmu_cdevt), 0),
rsmu, "rsmu%d", rsmu->index);
if (IS_ERR(rsmu_cdev)) {
dev_err(rsmu->dev, "Unable to create char device");
err = PTR_ERR(rsmu_cdev);
goto err_rsmu_cdev;
}

err = rsmu_init_ops(rsmu);
if (err) {
dev_err(rsmu->dev, "Unable to match type %d", rsmu->type);
goto err_rsmu_cdev;
}

dev_info(rsmu->dev, "Probe SMU type %d successful\n", rsmu->type);
return 0;

/* Failure cleanup */
err_rsmu_cdev:
cdev_del(&rsmu->rsmu_cdev);
err_rsmu_dev:
return err;
}

static int
rsmu_remove(struct platform_device *pdev)
{
struct rsmu_cdev *rsmu = platform_get_drvdata(pdev);
struct device *dev = &pdev->dev;

if (!rsmu)
return -ENODEV;

if (!rsmu_class) {
dev_err(dev, "rsmu_class is NULL");
return -EIO;
}

device_destroy(rsmu_class, MKDEV(MAJOR(rsmu_cdevt), 0));
cdev_del(&rsmu->rsmu_cdev);

return 0;
}

static const struct platform_device_id rsmu_id_table[] = {
{ "rsmu-cdev0", },
{ "rsmu-cdev1", },
{ "rsmu-cdev2", },
{ "rsmu-cdev3", },
{}
};
MODULE_DEVICE_TABLE(platform, rsmu_id_table);

static struct platform_driver rsmu_driver = {
.driver = {
.name = DRIVER_NAME,
},
.probe = rsmu_probe,
.remove = rsmu_remove,
.id_table = rsmu_id_table,
};

static int __init rsmu_init(void)
{
int err;

rsmu_class = class_create(THIS_MODULE, DRIVER_NAME);
if (IS_ERR(rsmu_class)) {
err = PTR_ERR(rsmu_class);
pr_err("Unable to register rsmu class");
return err;
}

err = alloc_chrdev_region(&rsmu_cdevt, 0, DRIVER_MAX_DEV, DRIVER_NAME);
if (err < 0) {
pr_err("Unable to get major number");
goto err_rsmu_class;
}

err = platform_driver_register(&rsmu_driver);
if (err < 0) {
pr_err("Unabled to register %s driver", DRIVER_NAME);
goto err_rsmu_drv;
}
return 0;

/* Error Path */
err_rsmu_drv:
unregister_chrdev_region(rsmu_cdevt, DRIVER_MAX_DEV);
err_rsmu_class:
class_destroy(rsmu_class);
return err;
}

static void __exit rsmu_exit(void)
{
platform_driver_unregister(&rsmu_driver);
unregister_chrdev_region(rsmu_cdevt, DRIVER_MAX_DEV);
class_destroy(rsmu_class);
rsmu_class = NULL;
}

module_init(rsmu_init);
module_exit(rsmu_exit);

MODULE_DESCRIPTION("Renesas SMU character device driver");
MODULE_LICENSE("GPL");

0 comments on commit 2b3ca22

Please sign in to comment.