cregit-Linux how code gets into the kernel

Release 4.14 arch/x86/platform/ts5500/ts5500.c

/*
 * Technologic Systems TS-5500 Single Board Computer support
 *
 * Copyright (C) 2013-2014 Savoir-faire Linux Inc.
 *      Vivien Didelot <vivien.didelot@savoirfairelinux.com>
 *
 * This program is free software; you can redistribute it and/or modify it under
 * the terms of the GNU General Public License as published by the Free Software
 * Foundation; either version 2 of the License, or (at your option) any later
 * version.
 *
 *
 * This driver registers the Technologic Systems TS-5500 Single Board Computer
 * (SBC) and its devices, and exposes information to userspace such as jumpers'
 * state or available options. For further information about sysfs entries, see
 * Documentation/ABI/testing/sysfs-platform-ts5500.
 *
 * This code may be extended to support similar x86-based platforms.
 * Actually, the TS-5500 and TS-5400 are supported.
 */

#include <linux/delay.h>
#include <linux/io.h>
#include <linux/kernel.h>
#include <linux/leds.h>
#include <linux/init.h>
#include <linux/platform_data/gpio-ts5500.h>
#include <linux/platform_data/max197.h>
#include <linux/platform_device.h>
#include <linux/slab.h>

/* Product code register */

#define TS5500_PRODUCT_CODE_ADDR	0x74

#define TS5500_PRODUCT_CODE		0x60	
/* TS-5500 product code */

#define TS5400_PRODUCT_CODE		0x40	
/* TS-5400 product code */

/* SRAM/RS-485/ADC options, and RS-485 RTS/Automatic RS-485 flags register */

#define TS5500_SRAM_RS485_ADC_ADDR	0x75

#define TS5500_SRAM			BIT(0)	
/* SRAM option */

#define TS5500_RS485			BIT(1)	
/* RS-485 option */

#define TS5500_ADC			BIT(2)	
/* A/D converter option */

#define TS5500_RS485_RTS		BIT(6)	
/* RTS for RS-485 */

#define TS5500_RS485_AUTO		BIT(7)	
/* Automatic RS-485 */

/* External Reset/Industrial Temperature Range options register */

#define TS5500_ERESET_ITR_ADDR		0x76

#define TS5500_ERESET			BIT(0)	
/* External Reset option */

#define TS5500_ITR			BIT(1)	
/* Indust. Temp. Range option */

/* LED/Jumpers register */

#define TS5500_LED_JP_ADDR		0x77

#define TS5500_LED			BIT(0)	
/* LED flag */

#define TS5500_JP1			BIT(1)	
/* Automatic CMOS */

#define TS5500_JP2			BIT(2)	
/* Enable Serial Console */

#define TS5500_JP3			BIT(3)	
/* Write Enable Drive A */

#define TS5500_JP4			BIT(4)	
/* Fast Console (115K baud) */

#define TS5500_JP5			BIT(5)	
/* User Jumper */

#define TS5500_JP6			BIT(6)	
/* Console on COM1 (req. JP2) */

#define TS5500_JP7			BIT(7)	
/* Undocumented (Unused) */

/* A/D Converter registers */

#define TS5500_ADC_CONV_BUSY_ADDR	0x195	
/* Conversion state register */

#define TS5500_ADC_CONV_BUSY		BIT(0)

#define TS5500_ADC_CONV_INIT_LSB_ADDR	0x196	
/* Start conv. / LSB register */

#define TS5500_ADC_CONV_MSB_ADDR	0x197	
/* MSB register */

#define TS5500_ADC_CONV_DELAY		12	
/* usec */

/**
 * struct ts5500_sbc - TS-5500 board description
 * @name:       Board model name.
 * @id:         Board product ID.
 * @sram:       Flag for SRAM option.
 * @rs485:      Flag for RS-485 option.
 * @adc:        Flag for Analog/Digital converter option.
 * @ereset:     Flag for External Reset option.
 * @itr:        Flag for Industrial Temperature Range option.
 * @jumpers:    Bitfield for jumpers' state.
 */

struct ts5500_sbc {
	
const char *name;
	
int	id;
	
bool	sram;
	
bool	rs485;
	
bool	adc;
	
bool	ereset;
	
bool	itr;
	
u8	jumpers;
};

/* Board signatures in BIOS shadow RAM */
static const struct {
	
const char * const string;
	
const ssize_t offset;

} ts5500_signatures[] __initconst = {
	{ "TS-5x00 AMD Elan", 0xb14 },
};


static int __init ts5500_check_signature(void) { void __iomem *bios; int i, ret = -ENODEV; bios = ioremap(0xf0000, 0x10000); if (!bios) return -ENOMEM; for (i = 0; i < ARRAY_SIZE(ts5500_signatures); i++) { if (check_signature(bios + ts5500_signatures[i].offset, ts5500_signatures[i].string, strlen(ts5500_signatures[i].string))) { ret = 0; break; } } iounmap(bios); return ret; }

Contributors

PersonTokensPropCommitsCommitProp
Vivien Didelot104100.00%1100.00%
Total104100.00%1100.00%


static int __init ts5500_detect_config(struct ts5500_sbc *sbc) { u8 tmp; int ret = 0; if (!request_region(TS5500_PRODUCT_CODE_ADDR, 4, "ts5500")) return -EBUSY; sbc->id = inb(TS5500_PRODUCT_CODE_ADDR); if (sbc->id == TS5500_PRODUCT_CODE) { sbc->name = "TS-5500"; } else if (sbc->id == TS5400_PRODUCT_CODE) { sbc->name = "TS-5400"; } else { pr_err("ts5500: unknown product code 0x%x\n", sbc->id); ret = -ENODEV; goto cleanup; } tmp = inb(TS5500_SRAM_RS485_ADC_ADDR); sbc->sram = tmp & TS5500_SRAM; sbc->rs485 = tmp & TS5500_RS485; sbc->adc = tmp & TS5500_ADC; tmp = inb(TS5500_ERESET_ITR_ADDR); sbc->ereset = tmp & TS5500_ERESET; sbc->itr = tmp & TS5500_ITR; tmp = inb(TS5500_LED_JP_ADDR); sbc->jumpers = tmp & ~TS5500_LED; cleanup: release_region(TS5500_PRODUCT_CODE_ADDR, 4); return ret; }

Contributors

PersonTokensPropCommitsCommitProp
Vivien Didelot180100.00%3100.00%
Total180100.00%3100.00%


static ssize_t name_show(struct device *dev, struct device_attribute *attr, char *buf) { struct ts5500_sbc *sbc = dev_get_drvdata(dev); return sprintf(buf, "%s\n", sbc->name); }

Contributors

PersonTokensPropCommitsCommitProp
Vivien Didelot42100.00%1100.00%
Total42100.00%1100.00%

static DEVICE_ATTR_RO(name);
static ssize_t id_show(struct device *dev, struct device_attribute *attr, char *buf) { struct ts5500_sbc *sbc = dev_get_drvdata(dev); return sprintf(buf, "0x%.2x\n", sbc->id); }

Contributors

PersonTokensPropCommitsCommitProp
Vivien Didelot42100.00%2100.00%
Total42100.00%2100.00%

static DEVICE_ATTR_RO(id);
static ssize_t jumpers_show(struct device *dev, struct device_attribute *attr, char *buf) { struct ts5500_sbc *sbc = dev_get_drvdata(dev); return sprintf(buf, "0x%.2x\n", sbc->jumpers >> 1); }

Contributors

PersonTokensPropCommitsCommitProp
Vivien Didelot44100.00%2100.00%
Total44100.00%2100.00%

static DEVICE_ATTR_RO(jumpers); #define TS5500_ATTR_BOOL(_field) \ static ssize_t _field##_show(struct device *dev, \ struct device_attribute *attr, char *buf) \ { \ struct ts5500_sbc *sbc = dev_get_drvdata(dev); \ \ return sprintf(buf, "%d\n", sbc->_field); \ } \ static DEVICE_ATTR_RO(_field) TS5500_ATTR_BOOL(sram); TS5500_ATTR_BOOL(rs485); TS5500_ATTR_BOOL(adc); TS5500_ATTR_BOOL(ereset); TS5500_ATTR_BOOL(itr); static struct attribute *ts5500_attributes[] = { &dev_attr_id.attr, &dev_attr_name.attr, &dev_attr_jumpers.attr, &dev_attr_sram.attr, &dev_attr_rs485.attr, &dev_attr_adc.attr, &dev_attr_ereset.attr, &dev_attr_itr.attr, NULL }; static const struct attribute_group ts5500_attr_group = { .attrs = ts5500_attributes, }; static struct resource ts5500_dio1_resource[] = { DEFINE_RES_IRQ_NAMED(7, "DIO1 interrupt"), }; static struct platform_device ts5500_dio1_pdev = { .name = "ts5500-dio1", .id = -1, .resource = ts5500_dio1_resource, .num_resources = 1, }; static struct resource ts5500_dio2_resource[] = { DEFINE_RES_IRQ_NAMED(6, "DIO2 interrupt"), }; static struct platform_device ts5500_dio2_pdev = { .name = "ts5500-dio2", .id = -1, .resource = ts5500_dio2_resource, .num_resources = 1, };
static void ts5500_led_set(struct led_classdev *led_cdev, enum led_brightness brightness) { outb(!!brightness, TS5500_LED_JP_ADDR); }

Contributors

PersonTokensPropCommitsCommitProp
Vivien Didelot24100.00%1100.00%
Total24100.00%1100.00%


static enum led_brightness ts5500_led_get(struct led_classdev *led_cdev) { return (inb(TS5500_LED_JP_ADDR) & TS5500_LED) ? LED_FULL : LED_OFF; }

Contributors

PersonTokensPropCommitsCommitProp
Vivien Didelot26100.00%1100.00%
Total26100.00%1100.00%

static struct led_classdev ts5500_led_cdev = { .name = "ts5500:green:", .brightness_set = ts5500_led_set, .brightness_get = ts5500_led_get, };
static int ts5500_adc_convert(u8 ctrl) { u8 lsb, msb; /* Start conversion (ensure the 3 MSB are set to 0) */ outb(ctrl & 0x1f, TS5500_ADC_CONV_INIT_LSB_ADDR); /* * The platform has CPLD logic driving the A/D converter. * The conversion must complete within 11 microseconds, * otherwise we have to re-initiate a conversion. */ udelay(TS5500_ADC_CONV_DELAY); if (inb(TS5500_ADC_CONV_BUSY_ADDR) & TS5500_ADC_CONV_BUSY) return -EBUSY; /* Read the raw data */ lsb = inb(TS5500_ADC_CONV_INIT_LSB_ADDR); msb = inb(TS5500_ADC_CONV_MSB_ADDR); return (msb << 8) | lsb; }

Contributors

PersonTokensPropCommitsCommitProp
Vivien Didelot67100.00%1100.00%
Total67100.00%1100.00%

static struct max197_platform_data ts5500_adc_pdata = { .convert = ts5500_adc_convert, }; static struct platform_device ts5500_adc_pdev = { .name = "max197", .id = -1, .dev = { .platform_data = &ts5500_adc_pdata, }, };
static int __init ts5500_init(void) { struct platform_device *pdev; struct ts5500_sbc *sbc; int err; /* * There is no DMI available or PCI bridge subvendor info, * only the BIOS provides a 16-bit identification call. * It is safer to find a signature in the BIOS shadow RAM. */ err = ts5500_check_signature(); if (err) return err; pdev = platform_device_register_simple("ts5500", -1, NULL, 0); if (IS_ERR(pdev)) return PTR_ERR(pdev); sbc = devm_kzalloc(&pdev->dev, sizeof(struct ts5500_sbc), GFP_KERNEL); if (!sbc) { err = -ENOMEM; goto error; } err = ts5500_detect_config(sbc); if (err) goto error; platform_set_drvdata(pdev, sbc); err = sysfs_create_group(&pdev->dev.kobj, &ts5500_attr_group); if (err) goto error; if (sbc->id == TS5500_PRODUCT_CODE) { ts5500_dio1_pdev.dev.parent = &pdev->dev; if (platform_device_register(&ts5500_dio1_pdev)) dev_warn(&pdev->dev, "DIO1 block registration failed\n"); ts5500_dio2_pdev.dev.parent = &pdev->dev; if (platform_device_register(&ts5500_dio2_pdev)) dev_warn(&pdev->dev, "DIO2 block registration failed\n"); } if (led_classdev_register(&pdev->dev, &ts5500_led_cdev)) dev_warn(&pdev->dev, "LED registration failed\n"); if (sbc->adc) { ts5500_adc_pdev.dev.parent = &pdev->dev; if (platform_device_register(&ts5500_adc_pdev)) dev_warn(&pdev->dev, "ADC registration failed\n"); } return 0; error: platform_device_unregister(pdev); return err; }

Contributors

PersonTokensPropCommitsCommitProp
Vivien Didelot279100.00%2100.00%
Total279100.00%2100.00%

device_initcall(ts5500_init);

Overall Contributors

PersonTokensPropCommitsCommitProp
Vivien Didelot129399.85%466.67%
Paul Gortmaker10.08%116.67%
Andi Kleen10.08%116.67%
Total1295100.00%6100.00%
Information contained on this website is for historical information purposes only and does not indicate or represent copyright ownership.
Created with cregit.