'''
DPRAM_gen.py
Author : Joshua Harthan
This script generates a device driver that interfaces with a DPRAM component
'''

n=512 # Change this value corresponding to the amount of registers for the DPRAM component

# File description
file=open("FE_DPRAM.c", "w+")
file.write("// SPDX-Licesne-Identifier: GPL-2.0+\n")
file.write("/** @file FE_DPRAM.c\n\n")
file.write("This kernel driver controls a Dual Port RAM block that interfaces with a programmable FIR filter\n\n")
file.write("@author Joshua Harthan (based on code written by Tyler Davis)\n")
file.write("@date 2020\n")
file.write("@copyright 2020 FlatEarth Inc, Bozeman MT\n")
file.write("*/\n\n")

# Includes
file.write("#include <linux/module.h>\n")
file.write("#include <linux/platform_device.h>\n")
file.write("#include <linux/io.h>\n")
file.write("#include <linux/fs.h>\n")
file.write("#include <linux/types.h>\n")
file.write("#include <linux/uaccess.h>\n")
file.write("#include <linux/init.h>\n")
file.write("#include <linux/cdev.h>\n")
file.write("#include <linux/regmap.h>\n")
file.write("#include <linux/of.h>\n")
file.write("#include \"custom_functions.h\" // used for fixed point operations\n\n")

# Linux header
file.write("/* Header for successful compilation */\n")
file.write("MODULE_LICENSE(\"GPL\");\n")
file.write("MODULE_AUTHOR(\"Joshua Harthan\");\n")
file.write("MODULE_DESCRIPTION(\"Loadable kernel module for Dual Port RAM, interfaceable with a programmable FIR filter.\");\n")
file.write("MODULE_VERSION(\"1.0\");\n\n")

# Fixed point number structure
file.write("/* Structure necessary for fixed point number reperesentation */\n")
file.write("struct fixed_num {\n")
file.write("  int integer;\n")
file.write("  int fraction;\n")
file.write("  int fraction_len;\n")
file.write("};\n\n")

# Register offsets
file.write("/* Register offsets of the DPRAM component */\n")
for i in range(n):
   file.write("#define REGISTER%d" % i + "_OFFSET " + hex(i) + "\n")

# Driver function prototypes
file.write("\n/* Driver function prototypes */\n")
file.write("static int DPRAM_probe		(struct platform_device *pdev);\n")
file.write("static int DPRAM_remove		(struct platform_device *pdev);\n")
file.write("static ssize_t DPRAM_read	(struct file *filp, char *buffer, size_t len, loff_t *offset);\n")
file.write("static ssize_t DPRAM_write	(struct file *filp, const char *buffer, size_t len, loff_t *offset);\n")
file.write("static int DPRAM_open		(struct inode *inode, struct file *file);\n")
file.write("static int DPRAM_release	(struct inode *inode, struct file *file);\n\n")

# FPGA device function prototypes
file.write("/* FPGA device functions */\n")
for i in range(n):
   file.write("static ssize_t register%d_write \t (struct device *dev, struct device_attribute *attr, const char *buf, size_t count);\n"  % i)
   file.write("static ssize_t register%d_read \t (struct device *dev, struct device_attribute *attr, char *buf);\n" % i)
file.write("static ssize_t name_read	         (struct device *dev, struct device_attribute *attr, char *buf);\n\n")

# Device attributes
file.write("/* Device attributes that show up in /sys/class for device */\n")
for i in range(n):
   file.write("DEVICE_ATTR (register%d," % i + "\t 0664, \t register%d_read," % i + "\t register%d_write);\n" % i)
file.write("DEVICE_ATTR (name,\t0444,\tname_read,\tNULL);\n\n")

# DPRAM device structure
file.write("/* Device struct */\n")
file.write("struct fe_DPRAM_dev {\n")
file.write("  struct cdev cdev;\n")
file.write("  char *name;\n")
file.write("  void __iomem *regs;\n")
for i in range(n):
   file.write("  int register%d;\n" % i)
file.write("};\n")
file.write("typedef struct fe_DPRAM_dev fe_DPRAM_dev_t;\n\n")

# Global variable declaration
file.write("/* Global Variables */\n")
file.write("static struct class *cl;  // Global variable for the device class\n")
file.write("static dev_t dev_num;\n\n")

# ID match struct
file.write("/* ID Matching struct to match driver to tree */\n")
file.write("static struct of_device_id fe_DPRAM_dt_ids[] = {\n")
file.write("  {\n")
file.write("    .compatible = \"dev,fe-dpram\"\n")
file.write("  },\n")
file.write("  { }\n")
file.write("};\n")
file.write("MODULE_DEVICE_TABLE(of, fe_DPRAM_dt_ids);\n\n")

# Platform driver struct
file.write("/* Platform driver struct */\n")
file.write("static struct platform_driver DPRAM_platform = {\n")
file.write("  .probe = DPRAM_probe,\n")
file.write("  .remove = DPRAM_remove,\n")
file.write("  .driver = {\n")
file.write("    .name = \"Flat Earth Dual Port RAM Driver\",\n")
file.write("    .owner = THIS_MODULE,\n")
file.write("    .of_match_table = fe_DPRAM_dt_ids\n")
file.write("  }\n")
file.write("};\n\n")

# File operations struct
file.write("/* File ops struct */\n");
file.write("static const struct file_operations fe_DPRAM_fops = {\n")
file.write("  .owner = THIS_MODULE,\n")
file.write("  .read = DPRAM_read,\n")
file.write("  .write = DPRAM_write,\n")
file.write("  .open = DPRAM_open,\n")
file.write("  .release = DPRAM_release\n")
file.write("};\n\n")

# Driver initialization
file.write("/* Initialize device driver */\n")
file.write("static int DPRAM_init(void) {\n")
file.write("  int ret_val = 0;\n\n")
file.write("  pr_info(\"Initializing the Flat Earth Dual Port RAM module...\\n\");\n\n")
file.write("  pr_info(\"Probe function start...\\n\");\n\n")
file.write("  // Register our driver with the \"Platform Driver\" bus\n")
file.write("  ret_val = platform_driver_register(&DPRAM_platform);\n")
file.write("  if (ret_val != 0) {\n")
file.write("    pr_err(\"platform_driver_register returned %d\\n\", ret_val);\n")
file.write("    return ret_val;\n")
file.write("  }\n")
file.write("  pr_info(\"Flat Earth Dual Port RAM module successfully initialized!\\n\");\n\n")
file.write("  return 0;\n")
file.write("}\n\n")

# Probe function
file.write("/* Probe function of device driver */\n")
file.write("static int DPRAM_probe(struct platform_device *pdev) {\n\n")
file.write("  int ret_val = -EBUSY;\n\n")
file.write("  char deviceName[20] = \"fe_DPRAM_\";\n")
file.write("  char deviceMinor[20];\n")
file.write("  int status;\n\n")
file.write("  struct device *device_obj;\n")
file.write("  fe_DPRAM_dev_t * fe_DPRAM_devp;\n\n")
file.write("  pr_info(\"DPRAM_probe enter...\\n\");\n\n")
file.write("  struct resource *r = 0;\n")
file.write("  r = platform_get_resource(pdev, IORESOURCE_MEM, 0);\n")
file.write("  if (r == NULL) {\n")
file.write("    pr_err(\"IORESOURCE_MEM (register space) does not exist\\n\");\n")
file.write("    goto bad_exit_return;  }\n\n")
file.write("  // Create structure to hold device information\n")
file.write("  fe_DPRAM_devp = devm_kzalloc(&pdev->dev, sizeof(fe_DPRAM_dev_t), GFP_KERNEL);\n")
file.write("  fe_DPRAM_devp->regs = devm_ioremap_resource(&pdev->dev, r);\n")
file.write("  if (IS_ERR(fe_DPRAM_devp->regs))\n")
file.write("    goto bad_ioremap;\n\n")
file.write("  platform_set_drvdata(pdev, (void *)fe_DPRAM_devp);\n")
file.write("  fe_DPRAM_devp->name = devm_kzalloc(&pdev->dev, 50, GFP_KERNEL);\n")
file.write("  if (fe_DPRAM_devp->name == NULL)\n")
file.write("    goto bad_mem_alloc;\n\n")
file.write("  strcpy(fe_DPRAM_devp->name, (char *)pdev->name);\n")
file.write("  pr_info(\"%s\\n\", (char *)pdev->name);\n")
file.write("  status = alloc_chrdev_region(&dev_num, 0, 1, \"fe_DPRAM_\");\n")
file.write("  if (status != 0)\n")
file.write("    goto bad_alloc_chrdev_region;\n\n")
file.write("  sprintf(deviceMinor, \"%d\", MAJOR(dev_num));\n")
file.write("  strcat(deviceName, deviceMinor);\n")
file.write("  pr_info(\"%s\\n\", deviceName);\n")
file.write("  cl = class_create(THIS_MODULE, deviceName);\n")
file.write("  if (cl == NULL)\n")
file.write("    goto bad_class_create;\n\n")
file.write("  cdev_init(&fe_DPRAM_devp->cdev, &fe_DPRAM_fops);\n")
file.write("  status = cdev_add(&fe_DPRAM_devp->cdev, dev_num, 1);\n")
file.write("  if (status != 0)\n")
file.write("    goto bad_cdev_add;\n\n")
file.write("  device_obj = device_create(cl, NULL, dev_num, NULL, deviceName);\n")
file.write("  if (device_obj == NULL)\n")
file.write("    goto bad_device_create;\n\n")
file.write("  dev_set_drvdata(device_obj, fe_DPRAM_devp);\n\n")
file.write("  // Check each register value, upon initialization reset the component\n")
for i in range(n):
   file.write("  status = device_create_file(device_obj, &dev_attr_register%d);\n" % i)
   file.write("  if(status)\n")
   file.write("    goto bad_device_create_file_%d;\n" % (i+1))
   file.write("\n")
file.write("  pr_info(\"DPRAM_probe exit\\n\");\n")
file.write("  return 0;\n\n")
file.write("  // Error functions for probe function\n")
for i in range((n-1), -1, -1):
   file.write("  bad_device_create_file_%d:\n" % (i + 1))
   file.write("    device_remove_file(device_obj, &dev_attr_register%d);\n" % i)
   file.write("\n")
file.write("  bad_device_create_file_0:\n")
file.write("    device_destroy(cl, dev_num);\n\n")
file.write("  bad_device_create:\n")
file.write("    cdev_del(&fe_DPRAM_devp->cdev);\n\n")
file.write("  bad_cdev_add:\n")
file.write("    class_destroy(cl);\n\n")
file.write("  bad_class_create:\n")
file.write("    unregister_chrdev_region(dev_num, 1);\n\n")
file.write("  bad_alloc_chrdev_region:\n\n")
file.write("  bad_mem_alloc:\n\n")
file.write("  bad_ioremap:\n")
file.write("    ret_val = PTR_ERR(fe_DPRAM_devp->regs);\n\n")
file.write("  bad_exit_return:\n")
file.write("    pr_info(\"DPRAM_probe bad exit\\n\");\n\n")
file.write("  return ret_val;\n")
file.write("}\n\n")

# FPGA functions
file.write("/* FPGA Attribute functions */\n")
for i in range(n):
   file.write("static ssize_t register%d_read(struct device *dev, struct device_attribute *attr, char *buf) { \n" % i)
   file.write("  fe_DPRAM_dev_t * devp = (fe_DPRAM_dev_t *)dev_get_drvdata(dev);\n")
   file.write("  fp_to_string(buf, devp->register%d, 0, true, 9);\n" % i)
   file.write("  strcat2(buf, \"\\n\");\n")
   file.write("  return strlen(buf);\n")
   file.write("}\n")
   file.write("\n")
   file.write("static ssize_t register%d_write(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { \n" % i)
   file.write("  uint32_t tempValue = 0;\n")
   file.write("  char substring[80];\n")
   file.write("  int substring_count = 0;\n")
   file.write("  int i;\n")
   file.write("  fe_DPRAM_dev_t *devp = (fe_DPRAM_dev_t *)dev_get_drvdata(dev);\n")
   file.write("  for (i = 0; i < count; i++) {\n")
   file.write("    if((buf[i] !=',') && (buf[i] != ' ') && (buf[i] !='\\0') && (buf[i] != '\\r') && (buf[i] != '\\n')) {\n")
   file.write("      substring[substring_count] = buf[i];\n")
   file.write("      substring_count++;\n")
   file.write("     }\n")
   file.write("   }\n")
   file.write("  substring[substring_count] = 0;\n")
   file.write("  tempValue = set_fixed_num(substring, 0, false);\n")
   file.write("  devp->register%d = tempValue;\n" % i)
   file.write("  iowrite32(devp->register%d," % i + "(u32 *)devp->regs + %d);" % i)
   file.write("  return count;\n")
   file.write("}\n")
   file.write("\n")
file.write("static ssize_t name_read(struct device *dev, struct device_attribute *attr, char *buf) {\n")
file.write("  fe_DPRAM_dev_t *devp = (fe_DPRAM_dev_t *)dev_get_drvdata(dev);\n")
file.write("  sprintf(buf, \"%s\\n\", devp->name);\n")
file.write("  return strlen(buf);\n")
file.write("}\n\n")

# Device driver functions
file.write("/* Device driver functions */\n")
file.write("static int DPRAM_open(struct inode *inode, struct file *file) {\n")
file.write("  pr_info(\"DPRAM : Device OPEN\\n\"); // Test\n")
file.write("  return 0;\n")
file.write("}\n\n")
file.write("static int DPRAM_release(struct inode *inode, struct file *file) {\n")
file.write("  pr_info(\"DPRAM : Device RELEASE\\n\"); // Test\n")
file.write("  return 0;\n")
file.write("}\n\n")
file.write("static ssize_t DPRAM_read(struct file *filp, char *buffer, size_t len, loff_t *offset) {\n")
file.write("  pr_info(\"DPRAM : Device READ\\n\"); // Test\n")
file.write("  return 0;\n")
file.write("}\n\n")
file.write("static ssize_t DPRAM_write(struct file *filp, const char *buffer, size_t len, loff_t *offset) {\n")
file.write("  pr_info(\"DPRAM : Device WRITE\\n\"); // Test\n")
file.write("  return 0;\n")
file.write("}\n\n")
file.write("static int DPRAM_remove(struct platform_device *pdev) {\n\n")
file.write("  fe_DPRAM_dev_t *dev = (fe_DPRAM_dev_t *)platform_get_drvdata(pdev);\n\n")
file.write("  pr_info(\"DPRAM_remove enter\\n\");\n\n")
file.write("  cdev_del(&dev->cdev);\n")
file.write("  unregister_chrdev_region(dev_num, 2);\n")
file.write("  iounmap(dev->regs);\n\n")
file.write("  pr_info(\"DPRAM_remove exit\\n\");\n\n")
file.write("  return 0;\n")
file.write("}\n\n")
file.write("static void DPRAM_exit(void) {\n")
file.write("  pr_info(\"Flat Earth Dual Port RAM module exit\\n\");\n")
file.write("  platform_driver_unregister(&DPRAM_platform);\n")
file.write("  pr_info(\"Flat Earth Dual Port RAM module successfully unregistered\\n\");\n")
file.write("}\n\n")

# End of driver
file.write("/* End of file */\n")
file.write("module_init(DPRAM_init);\n")
file.write("module_exit(DPRAM_exit);")

file.close()