mirror of
				git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
				synced 2025-09-04 20:19:47 +08:00 
			
		
		
		
	 50352fa730
			
		
	
	
		50352fa730
		
	
	
	
	
		
			
			This adds SPDX GPL-2.0 header to the Trace Hub driver and removes the GPLv2 boilerplate text. Signed-off-by: Alexander Shishkin <alexander.shishkin@linux.intel.com>
		
			
				
	
	
		
			346 lines
		
	
	
		
			7.1 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			346 lines
		
	
	
		
			7.1 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| // SPDX-License-Identifier: GPL-2.0
 | |
| /*
 | |
|  * Intel(R) Trace Hub PTI output driver
 | |
|  *
 | |
|  * Copyright (C) 2014-2016 Intel Corporation.
 | |
|  */
 | |
| 
 | |
| #define pr_fmt(fmt)	KBUILD_MODNAME ": " fmt
 | |
| 
 | |
| #include <linux/types.h>
 | |
| #include <linux/module.h>
 | |
| #include <linux/device.h>
 | |
| #include <linux/sizes.h>
 | |
| #include <linux/printk.h>
 | |
| #include <linux/slab.h>
 | |
| #include <linux/mm.h>
 | |
| #include <linux/io.h>
 | |
| 
 | |
| #include "intel_th.h"
 | |
| #include "pti.h"
 | |
| 
 | |
| struct pti_device {
 | |
| 	void __iomem		*base;
 | |
| 	struct intel_th_device	*thdev;
 | |
| 	unsigned int		mode;
 | |
| 	unsigned int		freeclk;
 | |
| 	unsigned int		clkdiv;
 | |
| 	unsigned int		patgen;
 | |
| 	unsigned int		lpp_dest_mask;
 | |
| 	unsigned int		lpp_dest;
 | |
| };
 | |
| 
 | |
| /* map PTI widths to MODE settings of PTI_CTL register */
 | |
| static const unsigned int pti_mode[] = {
 | |
| 	0, 4, 8, 0, 12, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0,
 | |
| };
 | |
| 
 | |
| static int pti_width_mode(unsigned int width)
 | |
| {
 | |
| 	int i;
 | |
| 
 | |
| 	for (i = 0; i < ARRAY_SIZE(pti_mode); i++)
 | |
| 		if (pti_mode[i] == width)
 | |
| 			return i;
 | |
| 
 | |
| 	return -EINVAL;
 | |
| }
 | |
| 
 | |
| static ssize_t mode_show(struct device *dev, struct device_attribute *attr,
 | |
| 			 char *buf)
 | |
| {
 | |
| 	struct pti_device *pti = dev_get_drvdata(dev);
 | |
| 
 | |
| 	return scnprintf(buf, PAGE_SIZE, "%d\n", pti_mode[pti->mode]);
 | |
| }
 | |
| 
 | |
| static ssize_t mode_store(struct device *dev, struct device_attribute *attr,
 | |
| 			  const char *buf, size_t size)
 | |
| {
 | |
| 	struct pti_device *pti = dev_get_drvdata(dev);
 | |
| 	unsigned long val;
 | |
| 	int ret;
 | |
| 
 | |
| 	ret = kstrtoul(buf, 10, &val);
 | |
| 	if (ret)
 | |
| 		return ret;
 | |
| 
 | |
| 	ret = pti_width_mode(val);
 | |
| 	if (ret < 0)
 | |
| 		return ret;
 | |
| 
 | |
| 	pti->mode = ret;
 | |
| 
 | |
| 	return size;
 | |
| }
 | |
| 
 | |
| static DEVICE_ATTR_RW(mode);
 | |
| 
 | |
| static ssize_t
 | |
| freerunning_clock_show(struct device *dev, struct device_attribute *attr,
 | |
| 		       char *buf)
 | |
| {
 | |
| 	struct pti_device *pti = dev_get_drvdata(dev);
 | |
| 
 | |
| 	return scnprintf(buf, PAGE_SIZE, "%d\n", pti->freeclk);
 | |
| }
 | |
| 
 | |
| static ssize_t
 | |
| freerunning_clock_store(struct device *dev, struct device_attribute *attr,
 | |
| 			const char *buf, size_t size)
 | |
| {
 | |
| 	struct pti_device *pti = dev_get_drvdata(dev);
 | |
| 	unsigned long val;
 | |
| 	int ret;
 | |
| 
 | |
| 	ret = kstrtoul(buf, 10, &val);
 | |
| 	if (ret)
 | |
| 		return ret;
 | |
| 
 | |
| 	pti->freeclk = !!val;
 | |
| 
 | |
| 	return size;
 | |
| }
 | |
| 
 | |
| static DEVICE_ATTR_RW(freerunning_clock);
 | |
| 
 | |
| static ssize_t
 | |
| clock_divider_show(struct device *dev, struct device_attribute *attr,
 | |
| 		   char *buf)
 | |
| {
 | |
| 	struct pti_device *pti = dev_get_drvdata(dev);
 | |
| 
 | |
| 	return scnprintf(buf, PAGE_SIZE, "%d\n", 1u << pti->clkdiv);
 | |
| }
 | |
| 
 | |
| static ssize_t
 | |
| clock_divider_store(struct device *dev, struct device_attribute *attr,
 | |
| 		    const char *buf, size_t size)
 | |
| {
 | |
| 	struct pti_device *pti = dev_get_drvdata(dev);
 | |
| 	unsigned long val;
 | |
| 	int ret;
 | |
| 
 | |
| 	ret = kstrtoul(buf, 10, &val);
 | |
| 	if (ret)
 | |
| 		return ret;
 | |
| 
 | |
| 	if (!is_power_of_2(val) || val > 8 || !val)
 | |
| 		return -EINVAL;
 | |
| 
 | |
| 	pti->clkdiv = val;
 | |
| 
 | |
| 	return size;
 | |
| }
 | |
| 
 | |
| static DEVICE_ATTR_RW(clock_divider);
 | |
| 
 | |
| static struct attribute *pti_output_attrs[] = {
 | |
| 	&dev_attr_mode.attr,
 | |
| 	&dev_attr_freerunning_clock.attr,
 | |
| 	&dev_attr_clock_divider.attr,
 | |
| 	NULL,
 | |
| };
 | |
| 
 | |
| static struct attribute_group pti_output_group = {
 | |
| 	.attrs	= pti_output_attrs,
 | |
| };
 | |
| 
 | |
| static int intel_th_pti_activate(struct intel_th_device *thdev)
 | |
| {
 | |
| 	struct pti_device *pti = dev_get_drvdata(&thdev->dev);
 | |
| 	u32 ctl = PTI_EN;
 | |
| 
 | |
| 	if (pti->patgen)
 | |
| 		ctl |= pti->patgen << __ffs(PTI_PATGENMODE);
 | |
| 	if (pti->freeclk)
 | |
| 		ctl |= PTI_FCEN;
 | |
| 	ctl |= pti->mode << __ffs(PTI_MODE);
 | |
| 	ctl |= pti->clkdiv << __ffs(PTI_CLKDIV);
 | |
| 	ctl |= pti->lpp_dest << __ffs(LPP_DEST);
 | |
| 
 | |
| 	iowrite32(ctl, pti->base + REG_PTI_CTL);
 | |
| 
 | |
| 	intel_th_trace_enable(thdev);
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static void intel_th_pti_deactivate(struct intel_th_device *thdev)
 | |
| {
 | |
| 	struct pti_device *pti = dev_get_drvdata(&thdev->dev);
 | |
| 
 | |
| 	intel_th_trace_disable(thdev);
 | |
| 
 | |
| 	iowrite32(0, pti->base + REG_PTI_CTL);
 | |
| }
 | |
| 
 | |
| static void read_hw_config(struct pti_device *pti)
 | |
| {
 | |
| 	u32 ctl = ioread32(pti->base + REG_PTI_CTL);
 | |
| 
 | |
| 	pti->mode	= (ctl & PTI_MODE) >> __ffs(PTI_MODE);
 | |
| 	pti->clkdiv	= (ctl & PTI_CLKDIV) >> __ffs(PTI_CLKDIV);
 | |
| 	pti->freeclk	= !!(ctl & PTI_FCEN);
 | |
| 
 | |
| 	if (!pti_mode[pti->mode])
 | |
| 		pti->mode = pti_width_mode(4);
 | |
| 	if (!pti->clkdiv)
 | |
| 		pti->clkdiv = 1;
 | |
| 
 | |
| 	if (pti->thdev->output.type == GTH_LPP) {
 | |
| 		if (ctl & LPP_PTIPRESENT)
 | |
| 			pti->lpp_dest_mask |= LPP_DEST_PTI;
 | |
| 		if (ctl & LPP_BSSBPRESENT)
 | |
| 			pti->lpp_dest_mask |= LPP_DEST_EXI;
 | |
| 		if (ctl & LPP_DEST)
 | |
| 			pti->lpp_dest = 1;
 | |
| 	}
 | |
| }
 | |
| 
 | |
| static int intel_th_pti_probe(struct intel_th_device *thdev)
 | |
| {
 | |
| 	struct device *dev = &thdev->dev;
 | |
| 	struct resource *res;
 | |
| 	struct pti_device *pti;
 | |
| 	void __iomem *base;
 | |
| 
 | |
| 	res = intel_th_device_get_resource(thdev, IORESOURCE_MEM, 0);
 | |
| 	if (!res)
 | |
| 		return -ENODEV;
 | |
| 
 | |
| 	base = devm_ioremap(dev, res->start, resource_size(res));
 | |
| 	if (!base)
 | |
| 		return -ENOMEM;
 | |
| 
 | |
| 	pti = devm_kzalloc(dev, sizeof(*pti), GFP_KERNEL);
 | |
| 	if (!pti)
 | |
| 		return -ENOMEM;
 | |
| 
 | |
| 	pti->thdev = thdev;
 | |
| 	pti->base = base;
 | |
| 
 | |
| 	read_hw_config(pti);
 | |
| 
 | |
| 	dev_set_drvdata(dev, pti);
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static void intel_th_pti_remove(struct intel_th_device *thdev)
 | |
| {
 | |
| }
 | |
| 
 | |
| static struct intel_th_driver intel_th_pti_driver = {
 | |
| 	.probe	= intel_th_pti_probe,
 | |
| 	.remove	= intel_th_pti_remove,
 | |
| 	.activate	= intel_th_pti_activate,
 | |
| 	.deactivate	= intel_th_pti_deactivate,
 | |
| 	.attr_group	= &pti_output_group,
 | |
| 	.driver	= {
 | |
| 		.name	= "pti",
 | |
| 		.owner	= THIS_MODULE,
 | |
| 	},
 | |
| };
 | |
| 
 | |
| static const char * const lpp_dest_str[] = { "pti", "exi" };
 | |
| 
 | |
| static ssize_t lpp_dest_show(struct device *dev, struct device_attribute *attr,
 | |
| 			     char *buf)
 | |
| {
 | |
| 	struct pti_device *pti = dev_get_drvdata(dev);
 | |
| 	ssize_t ret = 0;
 | |
| 	int i;
 | |
| 
 | |
| 	for (i = ARRAY_SIZE(lpp_dest_str) - 1; i >= 0; i--) {
 | |
| 		const char *fmt = pti->lpp_dest == i ? "[%s] " : "%s ";
 | |
| 
 | |
| 		if (!(pti->lpp_dest_mask & BIT(i)))
 | |
| 			continue;
 | |
| 
 | |
| 		ret += scnprintf(buf + ret, PAGE_SIZE - ret,
 | |
| 				 fmt, lpp_dest_str[i]);
 | |
| 	}
 | |
| 
 | |
| 	if (ret)
 | |
| 		buf[ret - 1] = '\n';
 | |
| 
 | |
| 	return ret;
 | |
| }
 | |
| 
 | |
| static ssize_t lpp_dest_store(struct device *dev, struct device_attribute *attr,
 | |
| 			      const char *buf, size_t size)
 | |
| {
 | |
| 	struct pti_device *pti = dev_get_drvdata(dev);
 | |
| 	ssize_t ret = -EINVAL;
 | |
| 	int i;
 | |
| 
 | |
| 	for (i = 0; i < ARRAY_SIZE(lpp_dest_str); i++)
 | |
| 		if (sysfs_streq(buf, lpp_dest_str[i]))
 | |
| 			break;
 | |
| 
 | |
| 	if (i < ARRAY_SIZE(lpp_dest_str) && pti->lpp_dest_mask & BIT(i)) {
 | |
| 		pti->lpp_dest = i;
 | |
| 		ret = size;
 | |
| 	}
 | |
| 
 | |
| 	return ret;
 | |
| }
 | |
| 
 | |
| static DEVICE_ATTR_RW(lpp_dest);
 | |
| 
 | |
| static struct attribute *lpp_output_attrs[] = {
 | |
| 	&dev_attr_mode.attr,
 | |
| 	&dev_attr_freerunning_clock.attr,
 | |
| 	&dev_attr_clock_divider.attr,
 | |
| 	&dev_attr_lpp_dest.attr,
 | |
| 	NULL,
 | |
| };
 | |
| 
 | |
| static struct attribute_group lpp_output_group = {
 | |
| 	.attrs	= lpp_output_attrs,
 | |
| };
 | |
| 
 | |
| static struct intel_th_driver intel_th_lpp_driver = {
 | |
| 	.probe		= intel_th_pti_probe,
 | |
| 	.remove		= intel_th_pti_remove,
 | |
| 	.activate	= intel_th_pti_activate,
 | |
| 	.deactivate	= intel_th_pti_deactivate,
 | |
| 	.attr_group	= &lpp_output_group,
 | |
| 	.driver	= {
 | |
| 		.name	= "lpp",
 | |
| 		.owner	= THIS_MODULE,
 | |
| 	},
 | |
| };
 | |
| 
 | |
| static int __init intel_th_pti_lpp_init(void)
 | |
| {
 | |
| 	int err;
 | |
| 
 | |
| 	err = intel_th_driver_register(&intel_th_pti_driver);
 | |
| 	if (err)
 | |
| 		return err;
 | |
| 
 | |
| 	err = intel_th_driver_register(&intel_th_lpp_driver);
 | |
| 	if (err) {
 | |
| 		intel_th_driver_unregister(&intel_th_pti_driver);
 | |
| 		return err;
 | |
| 	}
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| module_init(intel_th_pti_lpp_init);
 | |
| 
 | |
| static void __exit intel_th_pti_lpp_exit(void)
 | |
| {
 | |
| 	intel_th_driver_unregister(&intel_th_pti_driver);
 | |
| 	intel_th_driver_unregister(&intel_th_lpp_driver);
 | |
| }
 | |
| 
 | |
| module_exit(intel_th_pti_lpp_exit);
 | |
| 
 | |
| MODULE_LICENSE("GPL v2");
 | |
| MODULE_DESCRIPTION("Intel(R) Trace Hub PTI/LPP output driver");
 | |
| MODULE_AUTHOR("Alexander Shishkin <alexander.shishkin@linux.intel.com>");
 |